This article provides 31 Python Object-Oriented Programming (OOP) practice questions with solutions.
These exercises cover classes, attributes, methods, logic, inheritance, polymorphism, magic methods, encapsulation, type checking, and advance OOP concepts.
Each coding challenge includes a Practice Problem, Hint, Solution code, and detailed Explanation, ensuring you don’t just copy code, but genuinely practice and understand how and why it works.
- All solutions have been fully tested on Python 3.
- Use our Online Code Editor to solve these exercises in real time.
- If you have other solutions, please share them in the comments to help fellow developers.
Also See:
- Python OOP: Learn OOP (Object Oriented Programming) in Python to solve exercises.
- Python OOP Interview Questions
+ Table Of Contents (31 Exercises)
Table of contents
- Exercise 1: Define an Empty Vehicle Class
- Exercise 2: Vehicle Class with Instance Attributes
- Exercise 3: Rectangle Class with Area & Perimeter
- Exercise 4: Student Class with Average Grade
- Exercise 5: Product Class with Stock Value Calculator
- Exercise 6: Bank Account with Deposit & Overdraw Protection
- Exercise 7: Light Class with On/Off State Toggle
- Exercise 8: User Class with Password Validation
- Exercise 9: Temperature Class with Unit Converters
- Exercise 10: Notebook Class with Add & Display Notes
- Exercise 11: Coffee Machine with Multi-Resource Tracking
- Exercise 12: Shared Class Attribute Across Instances
- Exercise 13: Bus Subclass Inheriting from Vehicle
- Exercise 14: verride Parent Method Using super()
- Exercise 15: Add Maintenance Fee in Child Class via super()
- Exercise 16: Polymorphism with Dog & Cat speak()
- Exercise 17: Full-Time vs Part-Time Employee Pay Logic
- Exercise 18: Shape Subclasses with Custom area() Methods
- Exercise 19: Media Subclasses with Type-Specific Attributes
- Exercise 20: Discounted Order Subclass with 10% Off
- Exercise 21: Vehicle Class Hierarchy with Bike, Truck & Bus
- Exercise 22: Identify Object’s Class Using type()
- Exercise 23: Type Checking with isinstance() & issubclass()
- Exercise 24: Vector Addition Using add Overloading
- Exercise 25: Cart Length Using len Overloading
- Exercise 26: Private Balance with Property Getter & Setter
- Exercise 27: Callable Object Class Using call
- Exercise 28: Flight Class with Passenger Capacity Check
- Exercise 29: Zoo Class that Feeds All Animals
- Exercise 30: Character Class with Auto Level-Up Logic
- Exercise 31: Playlist Class with Add, Remove & Shuffle
Exercise 1: Define an Empty Vehicle Class
Problem Statement: Write a Python program to create a class named Vehicle that has no variables or methods defined inside it.
Purpose: This exercise introduces the most basic form of a Python class definition. It teaches you the required syntax for declaring a class and the use of the pass keyword as a placeholder, which is essential when you want to define an empty class or function body without causing a syntax error.
Given Input: No input required.
Expected Output: <class '__main__.Vehicle'>
Refer: Classes and Objects in Python
▼ Hint
- Use the
classkeyword followed by the class name and a colon. - Use
passinside the class body to make it valid Python without adding any attributes or methods. - After defining the class, you can confirm it exists by printing
Vehicledirectly.
▼ Solution & Explanation
Explanation:
class Vehicle:: Declares a new class namedVehicle. The colon marks the start of the class body.pass: A no-op placeholder that satisfies Python’s requirement for a non-empty class body. It does nothing at runtime but prevents aSyntaxError.print(Vehicle): Prints the class object itself, confirming it was created successfully. The output shows the class name and the module it belongs to (__main__when run as a script).
Exercise 2: Vehicle Class with Instance Attributes
Problem Statement: Write a Python program to create a Vehicle class with two instance attributes: max_speed and mileage. Create an object of the class and print both attributes.
Purpose: Learn to define instance attributes using the __init__ constructor method. Instance attributes are unique to each object, meaning different Vehicle objects can hold different values for speed and mileage. This is a foundational concept in object-oriented programming.
Given Input: vehicle1 = Vehicle("Tesla Model S", 250, 18)
Expected Output: Vehicle Name: Tesla Model S, Speed: 250, Mileage: 18
Refer:
▼ Hint
- Define an
__init__method that acceptsself,name,max_speed, andmileageas parameters. - Inside
__init__, assign each parameter toselfto store them as instance attributes. - Create an instance by calling
Vehicle(...)with the required arguments, then access attributes using dot notation.
▼ Solution & Explanation
Explanation:
def __init__(self, name, max_speed, mileage): The constructor method, called automatically when a new object is created.selfrefers to the specific instance being initialized.self.name = name: Binds the argument passed during object creation to the instance, making it accessible as an attribute on that object.vehicle1 = Vehicle("Tesla Model S", 250, 18): Creates a newVehicleinstance. Python passes the arguments to__init__automatically.vehicle1.max_speed: Dot notation is used to access instance attributes. Each object maintains its own copy of these values.
Exercise 3: Rectangle Class with Area & Perimeter
Problem Statement: Write a Python program to create a Rectangle class with length and width as instance attributes, and two methods: area() that returns the area and perimeter() that returns the perimeter.
Purpose: Learn how to add instance methods to a class. Methods allow objects to perform operations using their own data, which is a key principle of encapsulation in OOP. Calculating geometric properties is a clean, practical context for understanding how self connects methods to instance data.
Given Input: rect = Rectangle(10, 4)
Expected Output: Area = 40 and Perimeter = 28
▼ Hint
- Store
lengthandwidthas instance attributes inside__init__. - Define
area(self)that returnsself.length * self.width. - Define
perimeter(self)that returns2 * (self.length + self.width).
▼ Solution & Explanation
Explanation:
def area(self): An instance method that usesself.lengthandself.widthto compute and return the area. Theselfparameter gives the method access to the object’s own attributes.def perimeter(self): Applies the standard perimeter formula for a rectangle:2 * (length + width). Likearea(), it reads directly from instance attributes.rect.area(): Calling a method on an instance automatically passes that instance asself. You do not passselfexplicitly when calling the method.
Exercise 4: Student Class with Average Grade
Problem Statement: Write a Python program to create a Student class that stores a student’s name and a list of marks. Add a method average() that calculates and returns the average of all marks.
Purpose: This exercise shows how instance attributes can store complex data types such as lists, not just simple values. It also practices combining OOP with list operations and arithmetic, a pattern common in gradebooks, dashboards, and reporting tools.
Given Input: s1 = Student("Alice", [85, 90, 78, 92, 88])
Expected Output: Alice's Average Grade: 86.6
▼ Hint
- Accept
nameandmarks(a list) in the__init__method and assign them toself. - In the
average()method, usesum(self.marks) / len(self.marks)to compute the mean. - Use
round()if you want to control the number of decimal places in the output.
▼ Solution & Explanation
Explanation:
self.marks = marks: Stores the entire list as an instance attribute. EachStudentobject holds its own independent list of marks.sum(self.marks): Uses Python’s built-insum()to total all elements in the marks list without needing an explicit loop.len(self.marks): Returns the count of items in the list, used as the divisor to compute the mean. This works correctly regardless of how many marks are stored.s1.average(): Calls the method on the instance. The result is a float because Python 3’s/operator always returns a float.
Exercise 5: Product Class with Stock Value Calculator
Problem Statement: Write a Python program to create a Product class with three instance attributes: name, price, and quantity. Add a method total_value() that returns the total stock value by multiplying price by quantity.
Purpose: This exercise models a real-world business scenario using OOP. It reinforces how instance methods can derive new information from existing attributes, a pattern widely used in inventory management, e-commerce, and financial applications.
Given Input: p1 = Product("Laptop", 899.99, 5)
Expected Output: Total stock value of Laptop: $4499.95
▼ Hint
- Define
__init__withname,price, andquantityas parameters and assign each toself. - In
total_value(), returnself.price * self.quantity. - Use an f-string with
:.2fformatting to display the result as a currency value with two decimal places.
▼ Solution & Explanation
Explanation:
self.priceandself.quantity: Stored as instance attributes so eachProductobject independently tracks its own price and stock level.def total_value(self): A computed method that multipliesself.pricebyself.quantityto derive the total stock value. No external data is needed since all values are already on the instance.:.2f: A format specifier inside an f-string that rounds the float to exactly two decimal places, which is the standard format for displaying currency values.
Exercise 6: Bank Account with Deposit & Overdraw Protection
Problem Statement: Write a Python program to create a BankAccount class with a balance attribute and two methods: deposit(amount) that adds funds to the balance, and withdraw(amount) that deducts funds but prevents the balance from going below zero.
Purpose: Learn data validation and conditional logic inside instance methods. Preventing overdraw is a real-world business rule, and implementing it here teaches you how classes can enforce constraints on their own data, a core idea behind encapsulation in OOP.
Given Input: Starting balance of 1000, deposit 500, withdraw 200, then attempt to withdraw 2000.
Expected Output:
Balance after deposit: 1500 Balance after withdrawal: 1300 Insufficient funds. Current balance: 1300
▼ Hint
- Initialize
self.balancein__init__. - In
deposit(), add the amount directly toself.balance. - In
withdraw(), use anifstatement to check whetheramount <= self.balancebefore deducting. If not, print an insufficient funds message instead.
▼ Solution & Explanation
Explanation:
self.balance += amount: Thedeposit()method directly mutates the instance’sbalance. Because the attribute is stored onself, the change persists across all future method calls on that object.if amount <= self.balance: Guards the withdrawal by checking that sufficient funds exist before modifying the balance. This enforces the business rule that the balance cannot go negative.else: print(...): Provides feedback when a withdrawal is rejected. In a real application this could raise a custom exception instead, but a printed message is appropriate for an introductory exercise.
Exercise 7: Light Class with On/Off State Toggle
Problem Statement: Write a Python program to create a Light class with three methods: turn_on() that switches the light on, turn_off() that switches it off, and status() that reports whether the light is currently on or off.
Purpose: This exercise models a simple stateful object, where the object remembers and changes its own condition over time. It introduces the concept of state management within a class, a pattern found everywhere from UI components and IoT devices to game objects and workflow engines.
Given Input: Create a Light object, call turn_on(), check status(), call turn_off(), and check status() again.
Expected Output:
Light is ON Current status: ON Light is OFF Current status: OFF
▼ Hint
- Use a boolean attribute such as
self.is_on = Falsein__init__to track the current state. turn_on()should setself.is_on = Trueand print a confirmation message.turn_off()should setself.is_on = Falseand print a confirmation message.- In
status(), use a conditional to print"ON"or"OFF"based on the value ofself.is_on.
▼ Solution & Explanation
Explanation:
self.is_on = False: Sets the initial state of the light to off when the object is first created. Using a boolean is the most direct way to represent a two-state condition like on/off.turn_on()andturn_off(): Each method simply flipsself.is_onto the appropriate boolean value and prints a message. Because the value is stored onself, the change is retained for all subsequent method calls."ON" if self.is_on else "OFF": A Python ternary expression that converts the boolean state into a human-readable string. This is more concise than writing a fullif/elseblock for a simple two-branch output.
Exercise 8: User Class with Password Validation
Problem Statement: Write a Python program to create a User class that stores a username and a password. Add a check_password(input_password) method that returns True if the input matches the stored password, and False otherwise.
Purpose: This exercise introduces the idea of controlled access to sensitive data inside a class. Rather than exposing the password directly, the class provides a dedicated method to verify it. This pattern reflects a core principle of encapsulation in OOP, where internal data is protected and accessed only through defined interfaces.
Given Input: u1 = User("alice", "secure123")
Expected Output:
True
False
▼ Hint
- Store
usernameandpasswordas instance attributes in__init__. - In
check_password(self, input_password), compareinput_passwordtoself.passwordusing==and return the result directly. - Call the method with a correct password and then an incorrect one to verify both outcomes.
▼ Solution & Explanation
Explanation:
self.password = password: Stores the password as an instance attribute. In real applications you would never store a plain-text password; instead you would hash it using a library likebcrypt. Here, plain text is used to keep the focus on OOP fundamentals.def check_password(self, input_password): Accepts a candidate password and compares it to the stored one. Exposing a method rather than the attribute directly means the calling code never needs to touchself.passworditself.return self.password == input_password: The==comparison evaluates to a boolean, so the result can be returned directly without wrapping it in an explicitif/else.
Exercise 9: Temperature Class with Unit Converters
Problem Statement: Write a Python program to create a Temperature class that stores a temperature in Celsius. Add two methods: to_fahrenheit() that converts and returns the value in Fahrenheit, and to_kelvin() that converts and returns the value in Kelvin.
Purpose: This exercise demonstrates how a class can act as a data container with built-in conversion logic. It reinforces writing multiple methods that all operate on the same instance attribute, and applies straightforward mathematical formulas in a practical scientific context.
Given Input: t = Temperature(100)
Expected Output:
Celsius: 100 Fahrenheit: 212.0 Kelvin: 373.15
▼ Hint
- Store the Celsius value as
self.celsiusin__init__. - For Fahrenheit, use the formula:
(celsius * 9/5) + 32. - For Kelvin, use the formula:
celsius + 273.15.
▼ Solution & Explanation
Explanation:
self.celsius = celsius: The single source of truth for this object. Both conversion methods derive their results from this one attribute, so updating it would automatically affect all conversions.(self.celsius * 9 / 5) + 32: The standard Celsius-to-Fahrenheit formula. In Python 3,9 / 5evaluates to1.8as a float, so the result is always a decimal number.self.celsius + 273.15: The Celsius-to-Kelvin conversion adds the absolute zero offset. Adding a float literal ensures the return value is always a float, consistent with scientific notation.
Exercise 10: Notebook Class with Add & Display Notes
Problem Statement: Write a Python program to create a Notebook class that maintains an internal list of notes. Add an add_note(note) method that appends a new note to the list, and a show_notes() method that prints all stored notes.
Purpose: This exercise shows how a class can manage a growing collection of data over its lifetime. It practices initializing a mutable data structure inside __init__ and writing methods that both modify and read that structure, a pattern that appears in todo lists, message queues, logs, and many other applications.
Given Input: Add three notes: "Buy groceries", "Read a book", "Call the doctor".
Expected Output:
1. Buy groceries 2. Read a book 3. Call the doctor
▼ Hint
- Initialize
self.notes = []inside__init__so eachNotebookobject starts with its own empty list. - In
add_note(), useself.notes.append(note)to add the new entry. - In
show_notes(), useenumerate(self.notes, start=1)to print each note with a numbered prefix.
▼ Solution & Explanation
Explanation:
self.notes = []: Initializing the list inside__init__is critical. If it were defined at the class level instead, all instances would share the same list, causing notes from one notebook to appear in another.self.notes.append(note): Mutates the instance’s own list in place. Each call toadd_note()grows the list by one entry, and the change persists on the object until it is destroyed.enumerate(self.notes, start=1): Produces pairs of(index, value)starting from 1, allowing the loop to print a numbered list without manually tracking a counter variable.
Exercise 11: Coffee Machine with Multi-Resource Tracking
Problem Statement: Write a Python program to create a CoffeeMachine class that tracks three resource attributes: water, coffee, and milk (in ml/g). Add a make_latte() method that checks whether sufficient resources are available, deducts them if so, and prints an appropriate message in either case.
Purpose: This exercise combines state management, resource tracking, and conditional logic inside a single class. It mirrors how real-world stateful systems (vending machines, inventory systems, game resource managers) check preconditions before executing an action and update their internal state only when the action is valid.
Given Input: CoffeeMachine(water=300, coffee=100, milk=200). A latte requires 200ml water, 20g coffee, and 150ml milk.
Expected Output:
Latte made! Remaining - Water: 100ml, Coffee: 80g, Milk: 50ml Not enough resources to make a latte.
▼ Hint
- Store
water,coffee, andmilkas instance attributes in__init__. - In
make_latte(), define the required amounts as local variables and use a singleifcondition to check all three resources at once. - If the check passes, deduct the required amounts from each attribute and print the remaining levels. Otherwise, print a failure message.
▼ Solution & Explanation
Explanation:
water_needed,coffee_needed,milk_needed: Defined as local variables inside the method to represent the recipe. Keeping them local (rather than hardcoded inline) makes the method easier to read and the values easy to change in one place.if self.water >= water_needed and ...: All three resource checks are combined into one condition usingand. The deduction only happens when every condition is met, which prevents partial consumption of resources on a failed brew.self.water -= water_needed: Mutates the instance attribute in place. After a successful latte, the machine’s state is permanently updated, so a second call tomake_latte()reflects the reduced levels.- Second call to
make_latte(): With only100mlwater remaining (less than the required200ml), the condition fails and the insufficient resources message is printed, demonstrating state persistence across calls.
Exercise 12: Shared Class Attribute Across Instances
Problem Statement: Write a Python program to create a Vehicle class with a class attribute color = "White" that is shared by all instances. Create two vehicle objects and demonstrate that both share the same default color, then show that changing the class attribute updates all instances that have not overridden it.
Purpose: This exercise clarifies the distinction between class attributes and instance attributes. Class attributes are defined directly on the class and shared across every instance, making them ideal for default values or constants that apply universally. Understanding this difference prevents subtle bugs when mutable data is accidentally shared between objects.
Given Input: v1 = Vehicle("Tesla", 250) and v2 = Vehicle("BMW", 200).
Expected Output:
Tesla - Color: White, Speed: 250 BMW - Color: White, Speed: 200 Tesla - Color: Red, Speed: 250 BMW - Color: Red, Speed: 200
Refer: Python Class Variables
▼ Hint
- Define
color = "White"directly inside the class body, outside of any method, to make it a class attribute. - Instance attributes like
nameandmax_speedare still defined in__init__as usual. - To update the shared attribute for all instances, reassign it via the class itself:
Vehicle.color = "Red".
▼ Solution & Explanation
Explanation:
color = "White": Declared at the class level, outside__init__. This means it belongs to the class itself, not to any single instance. All objects share the same value unless they individually override it.v1.color: When Python looks upcoloron an instance and does not find it as an instance attribute, it walks up to the class and finds the class attribute there. This lookup chain is part of Python’s attribute resolution order.Vehicle.color = "Red": Reassigning via the class name updates the attribute at the class level, so all instances that still rely on the class attribute immediately reflect the new value. By contrast, doingv1.color = "Red"would only create a new instance attribute onv1, leavingv2unaffected.
Exercise 13: Bus Subclass Inheriting from Vehicle
Problem Statement: Write a Python program to create a Vehicle parent class with name and max_speed attributes and a display() method. Then create a Bus child class that inherits everything from Vehicle without adding anything new, and confirm that an instance of Bus can access the parent’s method.
Purpose: This exercise introduces inheritance, one of the four pillars of OOP. Inheritance lets a child class automatically receive all attributes and methods from its parent, promoting code reuse and expressing natural “is-a” relationships. A Bus is a Vehicle, so it makes sense for it to share the same interface.
Given Input: bus1 = Bus("School Bus", 120)
Expected Output: Vehicle: School Bus, Max Speed: 120 km/h
Refer: Inheritance in Python
▼ Hint
- To create a child class, pass the parent class as an argument in the class definition:
class Bus(Vehicle):. - If the child class adds nothing new, use
passin its body. - Create a
Businstance using the same arguments asVehicleand calldisplay()to confirm inheritance is working.
▼ Solution & Explanation
Explanation:
class Bus(Vehicle):: The parentheses indicate thatBusinherits fromVehicle. Python sets up the inheritance chain automatically, meaningBusgets all ofVehicle‘s attributes and methods for free.pass: SinceBusadds no new behavior at this stage,passis used as a placeholder. The class is still fully functional because everything it needs comes fromVehicle.bus1.display(): Python first looks fordisplayon theBusinstance, then on theBusclass, and finally onVehicle, where it finds and executes the method. This lookup process is known as the Method Resolution Order (MRO).
Exercise 14: verride Parent Method Using super()
Problem Statement: Write a Python program where a Vehicle parent class has a seating_capacity() method that accepts a capacity argument. Create a Bus child class that overrides this method to provide a default seating capacity of 50, using super() to call the parent’s version internally.
Purpose: This exercise covers method overriding and the use of super(), two key tools in OOP inheritance. Overriding lets a child class customize or extend a parent method’s behavior without rewriting it from scratch. super() delegates part of the work back to the parent, keeping the code DRY and maintaining the original logic as a foundation.
Given Input: bus = Bus("School Bus", 120)
Expected Output: School Bus seating capacity is: 50
▼ Hint
- Define
seating_capacity(self, capacity)in theVehicleclass and have it print a message using the capacity argument. - In the
Busclass, define a method with the same name but override it to callsuper().seating_capacity(50), passing the default value of50directly. - Call
bus.seating_capacity()on aBusinstance with no arguments to confirm that the default kicks in.
▼ Solution & Explanation
Explanation:
def seating_capacity(self, capacity)inVehicle: The parent defines the method to accept a flexible capacity value, keeping it general enough to work for any vehicle type.def seating_capacity(self)inBus: The child overrides the method with a version that takes no capacity argument. This is the override: whenseating_capacity()is called on aBusinstance, Python runs this version instead of the parent’s.super().seating_capacity(50):super()returns a proxy to the parent class, allowing the child to call the parent’s method directly. The hardcoded50is the bus-specific default, passed up to the parent’s implementation so the print logic stays in one place.bus.seating_capacity(): Called with no arguments on theBusinstance. The override intercepts the call, supplies the default of50, and delegates the actual output to the parent, combining both classes’ behavior cleanly.
Exercise 15: Add Maintenance Fee in Child Class via super()
Problem Statement: Write a Python program that creates a Vehicle parent class with a base fare, then extends a Taxi child class that adds a 10% maintenance fee on top of the base fare using super().
Purpose: This exercise teaches you how to use super() to call the parent class constructor, extend child class behaviour by building on inherited attributes, and model real-world pricing logic using inheritance.
Given Input: base_fare = 500
Expected Output: Total fare with maintenance fee: 550.0
▼ Hint
- Define a
Vehicleclass with an__init__that acceptsbase_fareand stores it as an instance attribute. - Create a
Taxiclass that inherits fromVehicle. - In
Taxi.__init__, callsuper().__init__(base_fare)to initialise the parent, then compute the maintenance fee asbase_fare * 0.10. - Add a method
total_fare()that returnsself.base_fare + self.maintenance_fee.
▼ Solution & Explanation
Explanation:
class Vehicle: Defines the parent class that accepts and storesbase_farein its constructor.super().__init__(base_fare): Calls the parent constructor from inside the child class, ensuringself.base_fareis properly set before the child adds its own logic.self.maintenance_fee = base_fare * 0.10: Computes the 10% maintenance fee and stores it as a separate child-only attribute.total_fare(): Returns the sum of the base fare and the maintenance fee, demonstrating how child classes can extend parent behaviour without modifying the parent.
Exercise 16: Polymorphism with Dog & Cat speak()
Problem Statement: Write a Python program that defines an Animal base class with a speak() method, then overrides it in Dog and Cat subclasses to return their respective sounds.
Purpose: This exercise introduces method overriding, one of the core pillars of polymorphism in OOP. It shows how different subclasses can share the same interface but provide their own specific behaviour.
Given Input: Objects of Dog and Cat classes
Expected Output:
Dog says: Woof!
Cat says: Meow!
Refer: Polymorphism in Python
▼ Hint
- Define an
Animalclass with aspeak()method that returns a generic string like"Some sound". - Create
DogandCatclasses that inherit fromAnimal. - Override
speak()in each subclass to return the appropriate sound string. - Instantiate both classes and call
speak()on each object to verify the output.
▼ Solution & Explanation
Explanation:
class Animal: Acts as the base class with a defaultspeak()method, establishing a common interface for all subclasses.class Dog(Animal): Inherits fromAnimaland overridesspeak()to return"Woof!", replacing the parent’s generic implementation.class Cat(Animal): Similarly overridesspeak()to return"Meow!".- Method overriding: When
speak()is called on aDogorCatobject, Python uses the subclass version, not the parent’s. This is the foundation of polymorphism.
Exercise 17: Full-Time vs Part-Time Employee Pay Logic
Problem Statement: Write a Python program that defines an Employee base class, then creates FullTimeEmployee and PartTimeEmployee subclasses, each implementing different pay calculation logic.
Purpose: This exercise models a common HR scenario and teaches you how to use inheritance to share common attributes while allowing each subclass to define its own business logic for calculating pay.
Given Input: FullTimeEmployee("Alice", 60000) and PartTimeEmployee("Bob", 500, 20)
Expected Output:
Alice's monthly pay: 5000.0
Bob's monthly pay: 10000
▼ Hint
- Define an
Employeebase class with__init__acceptingnameand acalculate_pay()method that can be left as a placeholder. - In
FullTimeEmployee, store the annualsalaryand compute monthly pay assalary / 12. - In
PartTimeEmployee, storehourly_rateandhours_worked, then compute pay as their product. - Override
calculate_pay()in each subclass with the appropriate formula.
▼ Solution & Explanation
Explanation:
class Employee: Serves as the base class holding the sharednameattribute and a defaultcalculate_pay()that returns0.FullTimeEmployee.calculate_pay(): Divides the annual salary by 12 to get monthly pay, using Python 3’s/operator which returns a float.PartTimeEmployee.calculate_pay(): Multiplieshourly_ratebyhours_worked, modelling a pay-per-hour contract.super().__init__(name): Used in both subclasses to delegate thenameassignment to the parent, avoiding code duplication.
Exercise 18: Shape Subclasses with Custom area() Methods
Problem Statement: Write a Python program that defines a Shape base class with an area() method, then implements it in Circle, Square, and Triangle subclasses using the appropriate geometric formulas.
Purpose: This exercise is a classic demonstration of polymorphism. Each shape shares the same area() interface but provides a completely different calculation, showing how OOP handles real-world variation cleanly.
Given Input: Circle(7), Square(4), Triangle(6, 8)
Expected Output:
Circle area: 153.94
Square area: 16
Triangle area: 24.0
▼ Hint
- Define a
Shapebase class with anarea()method that returns0as a placeholder. - For
Circle, use the formula3.14159 * radius ** 2. - For
Square, useside ** 2. - For
Triangle, use0.5 * base * height.
▼ Solution & Explanation
Explanation:
class Shape: Provides a common interface with a defaultarea()that returns0. All subclasses are expected to override this method.Circle.area(): Applies the formulapi * r^2. The result is rounded to 2 decimal places usinground()for clean output.Square.area(): Returnsside ** 2, the simplest area formula.type(shape).__name__: Dynamically retrieves the class name of each object at runtime, making the print loop reusable without hardcoding names.
Exercise 19: Media Subclasses with Type-Specific Attributes
Problem Statement: Write a Python program that defines a Media base class, then creates Book, Magazine, and DVD subclasses, each with type-specific attributes and a describe() method.
Purpose: This exercise shows how inheritance can model a taxonomy of related objects. Each media type shares a common identity (title, price) but carries unique attributes specific to its format, reflecting real-world library or inventory systems.
Given Input: Book("Clean Code", 499, "Robert C. Martin"), Magazine("Wired", 150, "Monthly"), DVD("Inception", 299, 148)
Expected Output:
Book: Clean Code by Robert C. Martin - Rs.499
Magazine: Wired (Monthly) - Rs.150
DVD: Inception, 148 mins - Rs.299
▼ Hint
- Define a
Mediabase class withtitleandpricein__init__. - Each subclass should call
super().__init__(title, price)and then add its own unique attribute:authorforBook,frequencyforMagazine, anddurationforDVD. - Override a
describe()method in each subclass to print a formatted string using the shared and unique attributes.
▼ Solution & Explanation
Explanation:
class Media: Stores the sharedtitleandpriceattributes common to all media types, and provides a genericdescribe()fallback.super().__init__(title, price): Used in every subclass to avoid repeating the assignment of shared attributes, keeping the code DRY (Don’t Repeat Yourself).- Unique attributes:
author,frequency, anddurationare subclass-specific and would not belong in the base class, since not all media types share them. - Iterating with
describe(): Calling the same method on different objects and getting different output is polymorphism in practice.
Exercise 20: Discounted Order Subclass with 10% Off
Problem Statement: Write a Python program that creates an Order class with a total amount, then creates a DiscountedOrder subclass that applies a 10% discount to the total.
Purpose: This exercise models a common e-commerce pattern and shows how a child class can extend a parent’s behaviour by modifying a calculated value, without changing the parent class at all.
Given Input: DiscountedOrder("ORD001", 1200)
Expected Output:
Order ID: ORD001
Original Total: 1200
Discounted Total: 1080.0
▼ Hint
- Define an
Orderclass withorder_idandtotalattributes and aget_total()method that returnsself.total. - In
DiscountedOrder, callsuper().__init__(order_id, total)and overrideget_total()to returnself.total * 0.90. - Print both the original
self.totaland the discounted result fromget_total()to show the difference.
▼ Solution & Explanation
Explanation:
class Order: The parent class storesorder_idandtotal, andget_total()simply returns the full amount with no modification.DiscountedOrder.get_total(): Overrides the parent method to apply a 10% reduction by multiplyingself.totalby0.90. The parent class is never modified.order.totalvsorder.get_total(): Accessingself.totaldirectly still returns the original value, whileget_total()returns the discounted one. This distinction is important for keeping the original data intact.- Open/Closed principle: The
Orderclass is closed for modification but open for extension.DiscountedOrderextends it without touching the original code.
Exercise 21: Vehicle Class Hierarchy with Bike, Truck & Bus
Problem Statement: Write a Python program that defines a Vehicle base class and creates Bike, Truck, and Bus subclasses, each defining a unique max_speed attribute and a describe() method.
Purpose: This exercise reinforces the concept of class hierarchies and shows how subclasses can specialise a shared blueprint with their own attribute values, reflecting how real-world transport systems are categorised.
Given Input: Objects of Bike, Truck, and Bus classes
Expected Output:
Bike max speed: 120 km/h
Truck max speed: 90 km/h
Bus max speed: 100 km/h
▼ Hint
- Define a
Vehiclebase class with amax_speedattribute set to0and adescribe()method that prints it. - Create
Bike,Truck, andBussubclasses, each setting their ownmax_speedin__init__. - Override
describe()in each subclass, or rely on the parent’s method if the output format is the same.
▼ Solution & Explanation
Explanation:
class Vehicle: Acceptsmax_speedas a parameter and provides a shareddescribe()method used by all subclasses.- Subclass constructors: Each subclass hardcodes its own
max_speedvalue and passes it up to the parent viasuper().__init__(). No extra attributes are needed in the subclasses. type(self).__name__: Inside the base classdescribe(), this retrieves the actual runtime class name (Bike,Truck, orBus), making the method reusable without overriding it in each subclass.- Looping over a mixed list: All three objects are stored in a single list and iterated uniformly, a direct example of polymorphic behaviour.
Exercise 22: Identify Object’s Class Using type()
Problem Statement: Write a Python program that creates objects from multiple classes and uses the built-in type() function to identify which class each object belongs to.
Purpose: This exercise teaches you how Python tracks the type of every object at runtime. Understanding type() is essential for debugging, dynamic dispatch, and writing flexible code that reacts differently based on the type of object it receives.
Given Input: Objects from Dog, Cat, and Vehicle classes
Expected Output:
d is of type: Dog
c is of type: Cat
v is of type: Vehicle
▼ Hint
- Define a few simple classes (they can have empty bodies using
pass). - Create one object from each class.
- Use
type(obj).__name__to get the class name as a string, or comparetype(obj)directly to the class itself (e.g.,type(obj) == Dog).
▼ Solution & Explanation
Explanation:
class Dog: pass: Thepasskeyword creates a valid but empty class body. This is useful when the structure of the class is not relevant to the exercise goal.type(obj): Returns the class (type object) thatobjwas created from. For example,type(d)returns<class '__main__.Dog'>.type(obj).__name__: The.__name__attribute on the returned type object gives just the plain string name of the class, such as"Dog", without the module prefix.- Alternative –
isinstance(): Whiletype()checks the exact class,isinstance(d, Dog)also returnsTruefor subclasses. Usetype()when you need an exact match, andisinstance()when inheritance should be considered.
Exercise 23: Type Checking with isinstance() & issubclass()
Problem Statement: Write a Python program that uses isinstance() to check whether an object is an instance of a given class, and issubclass() to check whether one class is a subclass of another.
Purpose: This exercise teaches you two of Python’s most important type-inspection tools. Unlike type(), both functions are inheritance-aware, making them essential for writing flexible, safe code that handles mixed object types gracefully.
Given Input: A Dog class inheriting from Animal, and an instance d = Dog()
Expected Output:
Is d an instance of Dog? True
Is d an instance of Animal? True
Is Dog a subclass of Animal? True
Is Animal a subclass of Dog? False
▼ Hint
- Define an
Animalbase class and aDogsubclass that inherits from it. - Use
isinstance(obj, ClassName)to check if an object is an instance of a class or any of its parent classes. - Use
issubclass(ChildClass, ParentClass)to check if one class inherits from another. - Note that
isinstance(d, Animal)returnsTrueeven thoughdwas created fromDog, becauseDoginherits fromAnimal.
▼ Solution & Explanation
Explanation:
isinstance(d, Dog): ReturnsTruebecausedwas directly created from theDogclass.isinstance(d, Animal): Also returnsTruebecauseDoginherits fromAnimal. This inheritance-awareness is what makesisinstance()more useful than a directtype()comparison in most real-world scenarios.issubclass(Dog, Animal): ReturnsTruebecauseDogis defined withAnimalas its parent. This works on classes themselves, not instances.issubclass(Animal, Dog): ReturnsFalsebecause the relationship is one-directional. The parent does not inherit from the child.
Exercise 24: Vector Addition Using add Overloading
Problem Statement: Write a Python program that creates a Vector class representing a 2D vector, and implements the __add__ dunder method so that two Vector objects can be added using the + operator.
Purpose: This exercise introduces operator overloading, a powerful OOP feature that lets your custom classes behave like built-in types. Implementing __add__ makes your objects integrate naturally with Python’s syntax.
Given Input: v1 = Vector(2, 3) and v2 = Vector(4, 1)
Expected Output: Vector(6, 4)
▼ Hint
- Define a
Vectorclass withxandyattributes in__init__. - Implement
__add__(self, other)to return a newVectorwhosexisself.x + other.xandyisself.y + other.y. - Implement
__repr__or__str__to control how the object is printed. - Test it by writing
v1 + v2and printing the result.
▼ Solution & Explanation
Explanation:
__add__(self, other): Python calls this method automatically when the+operator is used between twoVectorobjects.selfis the left operand andotheris the right.- Returns a new
Vector: Rather than modifyingselfin place, the method creates and returns a freshVectorobject. This keeps objects immutable under addition, which is the expected behaviour for mathematical types. __repr__: Defines the string representation of the object used byprint()and the interactive shell. Without it,print(result)would display something like<__main__.Vector object at 0x...>.- Operator overloading: The same pattern applies to other operators such as
__sub__for-,__mul__for*, and__eq__for==, making custom classes feel like native Python types.
Exercise 25: Cart Length Using len Overloading
Problem Statement: Write a Python program that creates a Cart class that stores a list of items, and implements __len__ so that calling len(cart) returns the number of items currently in the cart.
Purpose: This exercise introduces the __len__ dunder method, which lets your custom class integrate with Python’s built-in len() function. This is part of Python’s data model and makes your objects behave like native sequences or containers.
Given Input: A cart with items ["apple", "banana", "mango"]
Expected Output: Number of items in cart: 3
▼ Hint
- Define a
Cartclass with an__init__that initialises an empty listself.items = []. - Add an
add_item(item)method that appends toself.items. - Implement
__len__(self)to returnlen(self.items). - Once
__len__is defined, Python will use it whenever you calllen(cart)on your object.
▼ Solution & Explanation
Explanation:
self.items = []: Initialises the internal list that stores cart contents. Each instance gets its own independent list.__len__(self): Python calls this automatically whenlen(cart)is used. It delegates to the built-inlen()on the internal list, which already knows how to count its elements.- Protocol-based design: By implementing
__len__, yourCartobject now participates in Python’s sequence protocol. This also enables truthiness checks: an object with__len__returning0is treated asFalsein a boolean context. - Related dunder methods: You can extend this pattern with
__getitem__to support indexing (e.g.,cart[0]) and__iter__to support looping over the cart directly.
Exercise 26: Private Balance with Property Getter & Setter
Problem Statement: Write a Python program that creates a BankAccount class where the balance is stored as a private attribute __balance, and exposed safely through a @property getter and a setter that validates the value before updating it.
Purpose: This exercise demonstrates encapsulation, one of the four pillars of OOP. Using name-mangled private attributes alongside @property lets you control how external code reads and modifies internal state, preventing invalid data from being assigned.
Given Input: BankAccount(1000), then deposit 500, then attempt to set balance to -200
Expected Output:
Current balance: 1000
Current balance: 1500
Invalid balance. Must be non-negative.
▼ Hint
- Store the balance as
self.__balancein__init__. The double underscore triggers Python’s name mangling, making it harder to access directly from outside the class. - Define a
@propertymethod namedbalancethat returnsself.__balance. - Define a
@balance.setterthat checks if the new value is non-negative before assigning it, and prints an error message if not. - Add a
deposit(amount)method that updates the balance using the setter:self.balance = self.__balance + amount.
▼ Solution & Explanation
Explanation:
self.__balance: The double underscore prefix triggers name mangling. Python internally renames this to_BankAccount__balance, making it inaccessible asaccount.__balancefrom outside the class.@property: Turns thebalancemethod into a read-only attribute-style accessor. Callers writeaccount.balanceinstead ofaccount.balance().@balance.setter: Intercepts any assignment toaccount.balance = valueand runs the validation logic before updating__balance. This is where business rules are enforced.deposit()uses the setter: By writingself.balance = ...inside the method instead ofself.__balance = ..., the deposit operation still passes through the validation logic, keeping the rules consistent.
Exercise 27: Callable Object Class Using call
Problem Statement: Write a Python program that creates a Multiplier class which stores a factor, and implements __call__ so that an instance of the class can be invoked directly like a function to multiply a given number by that factor.
Purpose: This exercise introduces the __call__ dunder method, which makes any object callable. This pattern is commonly used in machine learning (layer objects), decorators, and anywhere you need a stateful function-like object.
Given Input: Multiplier(3) called with 10, and Multiplier(5) called with 7
Expected Output:
30
35
▼ Hint
- Define a
Multiplierclass with an__init__that stores the factor asself.factor. - Implement
__call__(self, value)to returnself.factor * value. - Create an instance with
triple = Multiplier(3), then call it like a function:triple(10).
▼ Solution & Explanation
Explanation:
self.factor: Stores the multiplier value at construction time. This is the state that distinguishes callable objects from plain functions: the object remembers its configuration between calls.__call__(self, value): Python invokes this method whenever the object is called using parentheses, such astriple(10). Without this method, doing so would raise aTypeError: 'Multiplier' object is not callable.- Callable objects vs. plain functions: A regular function cannot carry persistent state between calls without using global variables or closures. A callable class instance stores that state cleanly in its attributes.
- Real-world use: This pattern is used extensively in frameworks like PyTorch, where neural network layers are callable objects that store weights as instance attributes and process input data through
__call__.
Exercise 28: Flight Class with Passenger Capacity Check
Problem Statement: Write a Python program that creates a Passenger class and a Flight class. The Flight class should manage a list of Passenger objects and block further bookings when the seat capacity is reached.
Purpose: This exercise models a real-world object composition scenario where one class owns and manages a collection of another class. It also teaches boundary enforcement, where business rules (capacity limits) are built directly into the class methods.
Given Input: A Flight with capacity 2, then three booking attempts
Expected Output:
Alice booked on Flight AI202.
Bob booked on Flight AI202.
Sorry, Flight AI202 is fully booked.
▼ Hint
- Define a
Passengerclass with anameattribute. - Define a
Flightclass withflight_number,capacity, and an emptypassengerslist in__init__. - Add a
book(passenger)method that checkslen(self.passengers) < self.capacitybefore appending. - Print a confirmation message on success, or a “fully booked” message when capacity is exceeded.
▼ Solution & Explanation
Explanation:
class Passenger: A simple data class that holds a passenger’s name. In a larger system, it might also store a passport number, seat preference, or booking reference.self.passengers = []: EachFlightinstance maintains its own list. This is object composition: theFlightowns a collection ofPassengerobjects.- Capacity check in
book(): The guard conditionlen(self.passengers) < self.capacityenforces the business rule at the point of data entry, preventing the list from growing beyond its limit. - Passing objects as arguments:
flight.book(Passenger("Alice"))creates aPassengerobject inline and passes it directly. TheFlightmethod then works with the object’s attributes, demonstrating inter-object communication.
Exercise 29: Zoo Class that Feeds All Animals
Problem Statement: Write a Python program that defines an Animal base class with an eat() method, creates a few subclasses with their own eat() implementations, and builds a Zoo class that holds a list of animals and calls eat() on all of them via a feed_all() method.
Purpose: This exercise combines composition with polymorphism. The Zoo class does not need to know the specific type of each animal; it simply calls the shared eat() interface on every object in its collection, and each animal responds in its own way.
Given Input: A zoo containing a Lion, a Elephant, and a Parrot
Expected Output:
Lion eats meat.
Elephant eats grass.
Parrot eats seeds.
▼ Hint
- Define an
Animalbase class with aneat()method that returns a generic string. - Create
Lion,Elephant, andParrotsubclasses, each overridingeat()with a specific message. - Define a
Zooclass with anadd_animal(animal)method and afeed_all()method that loops through all stored animals and callseat()on each.
▼ Solution & Explanation
Explanation:
class Zoo: Acts as a container class that manages a heterogeneous collection ofAnimalobjects. It does not care about the specific subtype of each animal it holds.feed_all(): Iterates overself.animalsand callseat()on each. Because every animal overrideseat(), Python dispatches to the correct subclass method automatically. This is runtime polymorphism.- Composition over inheritance:
Zoodoes not inherit fromAnimal. Instead, it contains animals. This is a “has-a” relationship, as opposed to the “is-a” relationship of inheritance. - Extensibility: Adding a new animal type (e.g.,
Penguin) requires only creating a new subclass with its owneat(). TheZooandfeed_all()code need no changes.
Exercise 30: Character Class with Auto Level-Up Logic
Problem Statement: Write a Python program that creates a Character class with health, exp, and level attributes. The character should automatically level up and reset exp whenever accumulated experience reaches or exceeds 100.
Purpose: This exercise shows how to embed game logic directly into a class using a method that manages state transitions. It also demonstrates how to handle overflow (excess exp after levelling up) and keep multiple related attributes in sync.
Given Input: Character("Aria", health=100), then gain_exp(60) twice
Expected Output:
Aria gained 60 exp. (Total: 60)
Aria gained 60 exp. Level up! Now Level 2. (Remaining exp: 20)
▼ Hint
- Define
Character.__init__withname,health, and setself.exp = 0andself.level = 1as defaults. - In
gain_exp(amount), addamounttoself.exp, then check ifself.exp >= 100. - If a level-up occurs, increment
self.level, subtract 100 fromself.expto carry over the remainder, and print the level-up message. - If no level-up occurs, print a simpler message showing the current total exp.
▼ Solution & Explanation
Explanation:
self.exp = 0andself.level = 1: These default values are set in__init__rather than passed as parameters, since every new character reasonably starts at level 1 with zero experience.self.exp += amount: Accumulates experience over multiple calls. The running total is checked after every gain, not just once.self.exp -= 100: Subtracts exactly 100 instead of resetting to zero, so any exp earned beyond the threshold carries over to the next level. For example, gaining 60 exp on top of 60 gives 120 total; after levelling up, 20 exp remains.- Extending the logic: This pattern supports multiple level-ups in one call (e.g., gaining 250 exp at once) by changing the
ifto awhile self.exp >= 100loop, making the system robust for large exp rewards.
Exercise 31: Playlist Class with Add, Remove & Shuffle
Problem Statement: Write a Python program that defines a Song class and a Playlist class. The Playlist should support adding songs, removing songs by title, and shuffling the order of the playlist randomly.
Purpose: This exercise reinforces object composition, list manipulation, and the use of the standard library. Managing a collection of objects with add, remove, and reorder operations is a pattern found in media players, task managers, and many real-world applications.
Given Input: A playlist with three songs, then a removal and a shuffle
Expected Output:
Playlist: Blinding Lights, Levitating, Peaches
Removed: Levitating
After shuffle: (order will vary)
▼ Hint
- Define a
Songclass withtitleandartistattributes. - Define a
Playlistclass with an internalself.songs = []list and anadd_song(song)method that appends to it. - In
remove_song(title), use a list comprehension to filter out the song whosetitlematches the given string. - In
shuffle(), userandom.shuffle(self.songs)from therandommodule to randomise the order in place.
▼ Solution & Explanation
Explanation:
class Song: A lightweight data object holding a song’stitleandartist. ThePlayliststores these objects rather than plain strings, making it easy to display or filter by either attribute.remove_song()with list comprehension:[s for s in self.songs if s.title != title]builds a new list that excludes the matching song and reassigns it toself.songs. Comparing the before and after count confirms whether a removal actually occurred.random.shuffle(self.songs): Shuffles the list in place, meaning no new list is created. The order ofself.songsis randomised directly, and the output will differ on every run.display(): Uses a list comprehension to extract just the titles and joins them with', '.join(), producing a clean single-line summary of the playlist’s current state.
