Object-Oriented Programming~20 min

When OOP Helps — and When It Does Not

OOP earns its structure when a problem has real, changing state and behavior tied to it — not every problem does, and a plain function is often the clearer choice.

By the end of this lesson, you can

  • Solve the same small problem with and without a class, and compare the two
  • Identify signs that a problem doesn't need OOP — no meaningful changing state, no natural "thing" with behavior
  • Explain why OOP is one tool among several, not a mandatory starting point for every problem

Why it matters

Every class in this module — LibraryBook, Account, Animal and its subclasses — had something in common: real state that changed over time, with behavior tied to that changing state. Not every problem has that shape. Wrapping a plain calculation in a class adds structure — __init__, self, instantiation — without anything to show for it.

Mental model

Compare two solutions to the same problem: the area of a rectangle.

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

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


r = Rectangle(3, 4)
print(r.area())
PythonAs a plain function
def rectangle_area(width, height):
    return width * height

print(rectangle_area(3, 4))
Output
12

Both print 12. The function version does it in one line, with no __init__, no self, nothing to instantiate — because there’s nothing here that needs to be remembered between calls. A rectangle’s area is a pure calculation from its inputs, not a “thing” with state that changes over time.

Recognize the shape of the problem

Signs pointing toward — or away from — a class
QuestionIf yes, favorsIf no, favors
Does it have state that changes over multiple calls?A class (like is_on_loan)A plain function
Does behavior depend on that changing state?A class (like borrow() checking is_on_loan)A plain function
Are multiple independent instances needed?A class (each Account keeps its own balance)A plain function
Is it just input -> calculation -> output, nothing to remember?A plain functionA plain function either way

LibraryBook earned its class: is_on_loan genuinely changes over time, borrow()’s behavior depends on it, and different books need independent copies of that state. rectangle_area has none of that — every question above points toward a plain function.

Check your understanding

A function converts a temperature from Celsius to Fahrenheit, given a number, and returns a number. Does this need a class?
Or reveal the answer without checking

Answer:No — there's no state to track between calls; it's a pure calculation from input to output, which is exactly a plain function's job
Taking a parameter doesn't imply needing a class — a plain function takes parameters too. The question is whether there's state to remember between calls, and here there isn't.

What's the strongest signal that a class is actually justified for a problem?
Or reveal the answer without checking

Answer:That there's real state that changes over time, with behavior whose result depends on that changing state
Complexity and program size aren't the deciding factor — changing state with behavior tied to it is. LibraryBook needed a class because is_on_loan changes and borrow() depends on it; plenty of complicated-sounding problems still have no such state.

Practice: warm-up

For each, decide whether a class is justified or a plain function would be clearer, and say why: (1) validating whether an email address contains an @ symbol, (2) a shopping cart that items get added to and removed from over the course of a session, (3) converting a list of Fahrenheit temperatures to Celsius.

Stuck? Reveal one hint at a time.

  1. Hint 1

    Ask of each: is there state that persists and changes across multiple calls, or is it one calculation from input to output?

  2. Hint 2

    A shopping cart is the one where "what's currently in it" has to be remembered between separate add and remove actions.

Reveal the reasoning

Try the problem yourself before reading this. There is often more than one reasonable approach — treat this as one worked example, not the only correct answer.

1. Validate an email    -> plain function (pure check, no state to remember)
2. Shopping cart         -> class (contents change over time; add/remove depend on current state)
3. Convert temperatures  -> plain function (one calculation per value, nothing to remember)

Practice: apply it

This class wraps a calculation that has no state to track at all:

class DiscountCalculator:
    def __init__(self):
        pass

    def apply(self, price, rate):
        return price * (1 - rate)


calc = DiscountCalculator()
print(calc.apply(100, 0.2))
What does this class add over a plain function here?
Or reveal the answer without checking

Answer:Nothing — __init__ does nothing, and apply() doesn't use self at all, so this is a function wearing unnecessary class structure
__init__ is empty, and apply() never reads or writes any instance state (self is never used inside it) — every question from this lesson's table points toward a plain function instead.

Modification challenge: rewrite DiscountCalculator as a plain function, apply_discount(price, rate), and confirm it produces the same result with none of the class machinery.

Summary

  • A class earns its structure when a problem has real state that changes over time, with behavior that depends on that state — not by default.
  • A pure calculation from input to output, with nothing to remember between calls, is usually clearer as a plain function.
  • LibraryBook, Account, and the Animal hierarchy all had genuinely changing state; not every problem does.
  • OOP is one tool among several — procedural functions remain the right choice whenever there’s no meaningful state to bundle.