Skip to content
beginner

Practice Exercises: Object-Oriented Programming

You've read about classes and objects. The ideas make sense on the page. A class is a blueprint. An object is built from that blueprint. self carries each…

Published 2026-09-05Updated 2026-09-129 min read
Close-up of a textured carpet showcasing a teal square amidst gray tones.
Close-up of a textured carpet showcasing a teal square amidst gray tones. Photo by Beyzanur K. on Pexels.

You've read about classes and objects. The ideas make sense on the page. A class is a blueprint. An object is built from that blueprint. self carries each object's own data. Then you close the tutorial, open a blank file, and freeze.

That gap—between I read it and I can write it—is exactly what these exercises are for. Reading OOP is passive. The concepts only stick when you write a class, run it, and watch the output confirm (or betray) your mental model.

Before You Start

These exercises assume you already know the basics: a class is a blueprint, and an object is an instance built from that blueprint. If those terms feel shaky, review the OOP basics article first, then come back here.

Each exercise follows the same shape:

  1. Goal — what you're building
  2. Starter code — a partial class to complete
  3. Expected behavior — what your output should look like
  4. Hint — a nudge if you're stuck
  5. Solution — the completed code
  6. Stretch — a challenge that changes one requirement

Here's the important part: write the code yourself before looking at the solution. Type the starter code into a .py file, run it, and compare your output to the expected result. Then check the solution. If your version works and produces the same output, you've learned something. If it doesn't, the error message is your teacher.

Exercise 1: Build a Simple Class

Goal: Create a Dog class with a name attribute and a method that returns a friendly description.

Starter code:

class Dog:
    def __init__(self, name):
        # TODO: store the dog's name

    def describe(self):
        # TODO: return a string like "Rex is a good dog."

Expected behavior:

Rex is a good dog.

Hint: __init__ runs automatically when you create an object. Inside it, self.name = name stores the name on the object. The describe method should return a string, not print one.

Try it yourself. Run the starter code. You'll get a TypeError or a missing value because the methods don't do anything yet. That's fine—the error tells you exactly which piece is missing. Fill in the TODOs, run the code again, and compare your output to the expected behavior.

Solution:

class Dog:
    def __init__(self, name):
        self.name = name

    def describe(self):
        return f"{self.name} is a good dog."

dog = Dog("Rex")
print(dog.describe())

Expected output:

Rex is a good dog.

Why return and not print? If describe printed the string, you couldn't store it, format it, or combine it with other text. Returning gives the caller control. The print() call outside the class decides what happens to the value. That separation matters more as your programs grow.

Stretch: Add a breed attribute and update describe to include it. The expected output should look like Rex is a good German Shepherd. Hint: you'll need to add a second parameter to __init__ and pass it when you create the dog.

Solution for the stretch:

class Dog:
    def __init__(self, name, breed):
        self.name = name
        self.breed = breed

    def describe(self):
        return f"{self.name} is a good {self.breed}."

dog = Dog("Rex", "German Shepherd")
print(dog.describe())

Expected output:

Rex is a good German Shepherd.

This is the smallest possible win: you defined a class, created an object, and called a method. Notice that dog and another dog created from the same class would each carry their own name and breed. That's the core of OOP—one blueprint, many independent objects.

Knowledge check

Check your understanding

Answer this question before you continue.

Why should `Dog.describe()` return its string instead of printing it inside the method?
Single Choice

Focus: Distinguish returning a method result from printing it inside the class.

Exercise 2: Add Methods That Do Work

Goal: Build a Rectangle class that stores width and height, with methods that return the area and perimeter.

Starter code:

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        # TODO: return width times height

    def perimeter(self):
        # TODO: return 2 times (width plus height)

Expected behavior:

Area: 12
Perimeter: 14

Hint: Methods can read self.width and self.height just like any other variable. Both methods should return a number, not print it.

Try it yourself. Run the starter code and call rect.area(). What happens? The method exists but returns nothing, so Python gives you None. That missing value is your clue: the method needs a return statement, not just a calculation.

Solution:

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)

rect = Rectangle(3, 4)
print("Area:", rect.area())
print("Perimeter:", rect.perimeter())

Expected output:

Area: 12
Perimeter: 14

Stretch: Add a method is_square() that returns True if the rectangle is a square and False otherwise. Hint: a square has equal width and height. Test it with Rectangle(5, 5) and Rectangle(3, 4).

Solution for the stretch:

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)

    def is_square(self):
        return self.width == self.height

square = Rectangle(5, 5)
print("Square:", square.is_square())
rect = Rectangle(3, 4)
print("Square:", rect.is_square())

Expected output:

Square: True
Square: False

This exercise shows the real pattern of OOP: methods turn stored data into useful answers. The object holds the data; the methods do the work. That division—data plus behavior bundled together—is what makes OOP a different way of thinking than plain functions operating on plain variables.

Knowledge check

Check your understanding

Answer this question before you continue.

In the starter `Rectangle` class, what value does `rect.area()` produce before the `area` method is completed with a `return` statement?
Output Prediction

Focus: Predict the result of calling a method that performs a calculation without returning it.

`rect = Rectangle(3, 4)`

Exercise 3: Track Changing State

A left-to-right sequence showing a BankAccount created with balance 50, deposit(50) changing the balance to 100, and withdraw(25) changing it to 75; each method call points to the account's current balance.
The object keeps its balance between method calls, and each method updates that shared state.

Goal: Create a BankAccount class that starts with a balance and supports deposit and withdraw methods.

Starter code:

class BankAccount:
    def __init__(self, starting_balance):
        self.balance = starting_balance

    def deposit(self, amount):
        # TODO: add amount to the balance

    def withdraw(self, amount):
        # TODO: subtract amount from the balance

Expected behavior:

Balance: 100
Balance: 75

Hint: deposit and withdraw should update self.balance, not return a new number and forget it. Use self.balance = self.balance + amount (or the shorthand self.balance += amount).

Try it yourself. Run the starter code, then call account.deposit(50) and check account.balance. What value do you see? If the balance didn't change, the method didn't update the object's state. That observation is the whole lesson: methods that change an object must assign back to self.balance.

Solution:

class BankAccount:
    def __init__(self, starting_balance):
        self.balance = starting_balance

    def deposit(self, amount):
        self.balance += amount

    def withdraw(self, amount):
        self.balance -= amount

account = BankAccount(50)
account.deposit(50)
print("Balance:", account.balance)
account.withdraw(25)
print("Balance:", account.balance)

Expected output:

Balance: 100
Balance: 75

Stretch: Modify withdraw so it prints a warning instead of letting the balance go below zero. Hint: check whether amount is greater than self.balance before subtracting.

Solution for the stretch:

class BankAccount:
    def __init__(self, starting_balance):
        self.balance = starting_balance

    def deposit(self, amount):
        self.balance += amount

    def withdraw(self, amount):
        if amount > self.balance:
            print("Warning: insufficient funds.")
        else:
            self.balance -= amount

account = BankAccount(50)
account.withdraw(75)
print("Balance:", account.balance)
account.deposit(100)
account.withdraw(25)
print("Balance:", account.balance)

Expected output:

Warning: insufficient funds.
Balance: 50
Balance: 125

This is the exercise where the mental model clicks. The object remembers its own state between method calls. When you call account.deposit(50), the account itself changes. The next time you check account.balance, you see the updated value. That's not a variable being passed around—that's an object carrying its own history.

Knowledge check

Check your understanding

Answer this question before you continue.

Which implementation correctly makes `deposit` update the account's balance?
Debugging

Focus: Repair a method so it updates an object's stored state between calls.

```python
class BankAccount:
    def __init__(self, starting_balance):
        self.balance = starting_balance

    def deposit(self, amount):
        # choose the replacement for this line
```

Common Mistakes to Watch For

These errors are normal. Every beginner hits them. Here's what they look like and how to fix them.

Forgetting self as the first parameter. Every method inside a class needs self as its first parameter, even if it doesn't use it.

# Wrong
def describe():
    return "Hello"

# Right
def describe(self):
    return "Hello"

If you forget it, Python will complain about passing the wrong number of arguments when you call the method.

Returning a value but never printing or storing it. A method that returns a number doesn't display anything. If you call rect.area() without print(), nothing appears to happen. The value is returned and then discarded.

Using an attribute before __init__ sets it. If you try to read self.balance before __init__ has assigned it, Python raises an AttributeError. Make sure every attribute you use is created in __init__.

Confusing the class with the object. Dog is the class. dog = Dog("Rex") creates the object. You call methods on the object (dog.describe()), not on the class.

Knowledge check

Check your understanding

Answer this question before you continue.

A method is defined inside a class as `def describe():`. What change does the article identify as necessary for calling it on an object?
Misconception Check

Focus: Recognize that instance methods need `self` as their first parameter.

Put It Together: A Small Data Cleanup Task

Now use the same pattern in a realistic scenario. Imagine you're cleaning up a list of product records. Each product has a name, a price, and a quantity in stock. You want to calculate the total value of each product.

Your task: Create a Product class with name, price, and quantity attributes. Add a method total_value() that returns price * quantity. Then loop through this list and print the total value for each product:

products = [
    Product("Laptop", 899.99, 5),
    Product("Mouse", 24.50, 20),
    Product("Keyboard", 79.00, 10),
]

Expected behavior:

Laptop: $4499.95
Mouse: $490.00
Keyboard: $790.00

Hint: You'll need a for loop that calls total_value() on each product. Use an f-string to format the output. This task uses everything from the three exercises: __init__, attributes, methods that compute from stored data, and objects working together in a list.

Solution:

class Product:
    def __init__(self, name, price, quantity):
        self.name = name
        self.price = price
        self.quantity = quantity

    def total_value(self):
        return self.price * self.quantity

products = [
    Product("Laptop", 899.99, 5),
    Product("Mouse", 24.50, 20),
    Product("Keyboard", 79.00, 10),
]

for product in products:
    print(f"{product.name}: ${product.total_value():.2f}")

Expected output:

Laptop: $4499.95
Mouse: $490.00
Keyboard: $790.00

This is the same pattern you'll use in real scripts: define a class that holds data, give it methods that answer questions about that data, then process a collection of objects. Inventory reports, log analyzers, and data cleanup scripts all follow this shape.

What to Practice Next

Here's your next move: close this article and redo all three exercises from a blank file. No peeking at the solutions. If you can write a class with __init__, create an object, and call methods that update state—you've got it.

When you're ready to go deeper, look at how attributes and methods work together in more detail. Then tackle inheritance, which lets one class reuse and extend another. That's the natural next step once classes, objects, and methods feel comfortable.

The fastest way to make OOP stick is to write a class, run it, break it, and fix it. Output is the teacher. Every error message tells you something about what your code is actually doing—and every fix builds the mental model reading alone never will.

Knowledge check

Final check

Finish the article by checking the ideas you just learned.

What line does the cleanup task print for the mouse product?
Question 1 of 2Output Prediction

Focus: Predict a product total by applying a method to stored price and quantity data.

The product is created as `Product("Mouse", 24.50, 20)`, and the loop prints `f"{product.name}: ${product.total_value():.2f}"`.
Which workflow best matches the pattern practiced throughout the article?
Question 2 of 2Single Choice

Focus: Identify the recurring OOP pattern of combining stored data, behavior, and collections of objects.

References

  1. 30+ Python OOP Exercise – Classes and Objects Exercisespynative.com
  2. Python Basics Exercises: Object-Oriented Programming – Real Pythonrealpython.com
8sources checked
8source domains
5searches run

Research updated Sep 5, 2026

Practical resource

Want a more structured Python path?

Use the Python Starter Pack to turn scattered tutorials into a focused practice path.

View the bundle
Coming soon

Python Starter Pack

A compact LearnPyFast PDF pack covering what Python is, installation, your first program, running Python code, and Python versions.

$9
PDF BundleTopic PackPythonBeginner
  • 5 curated chapters
  • Enhanced PDF edition with bundle-only learning guidance
  • Offline-friendly format for focused review
  • Source article links for future online updates

Coming soon

Free Python bundle

Get the LearnPyFast Python Starter Bundle

A focused collection of beginner-friendly Python resources to help you move from setup to building practical projects.

You’ll receive the bundle by email. You can unsubscribe anytime.

No spam. You can unsubscribe anytime. See our Privacy policy.

Related sites

Continue beyond Python

Explore related Worldmonger sites when you want to move from Python basics into JavaScript or LLM application building.

JavaScript tutorialstutorial

LearnJSFast

Beginner-friendly JavaScript tutorials for practical web development and self-taught developers.

JavaScriptFrontendWeb development
Visit LearnJSFast
LLM tutorialstutorial

LearnLLMFast

Practical LLM tutorials for builders who want to understand prompting, workflows, agents, and AI applications.

LLMAIBuilders
Visit LearnLLMFast

Keep learning

Related tutorials

Continue with nearby Python topics and beginner-friendly explanations.