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.
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())def rectangle_area(width, height):
return width * height
print(rectangle_area(3, 4))12Both 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
| Question | If yes, favors | If 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 function | A 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
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.
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.
Hint 1
Ask of each: is there state that persists and changes across multiple calls, or is it one calculation from input to output?
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))
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 theAnimalhierarchy 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.