Before you start
Lesson 1 was about the mechanics — how to write a class and create objects. Lesson 2 is about the first big idea you'll use in every class you write: encapsulation.
The one-sentence version
Hide the data. Show only the actions.
Everything else in this lesson comes from that one sentence.
Why this matters in design interviews
Every design problem has rules that must always be true. A parking lot can't have negative spots. A Splitwise balance sheet must always add up to zero. A vending machine can't give you a snack it doesn't have.
Where do these rules live in your code?
- Weak answer: "in the caller." Every place that touches the data has to remember the rules. This causes bugs.
- Strong answer: "in the class." The class has methods that check the rules. Nobody outside the class can break them.
Encapsulation is how you do this. This lesson practices on a
BankAccount because bank accounts have obvious rules
(balance can't go negative, no free deposits, no overdrafts).
Every design problem you'll meet later has less-obvious
rules — but the same technique works.
Python's honor system
Python doesn't have private like Java does. A leading
underscore (_balance) is just a convention that means
"internal — don't touch". Nothing stops you.
Why work this way?
- You can still test, mock, and inspect internals when you really need to. Helps with debugging.
- The convention is everywhere. Every Python codebase you'll
ever read treats
_fooas internal. - Truly hidden data exists too (
__balancewith two underscores triggers something called "name mangling") but is almost never needed. Save it for advanced library authors.
About @property
@property turns a method into something that looks like an
attribute when you read it:
a.balance # calls balance(self) internally, returns the value
a.balance = 5 # AttributeError — no setter defined
You could write a get_balance() method instead. Python
prefers the property style. It lets you start with a plain
attribute and add validation later without breaking any
caller. Writing Java-style getters everywhere is a code
smell in Python.
What you're building
A BankAccount that:
- Rejects impossible starting balances
- Only accepts positive deposits and withdrawals
- Refuses to let you take out more than you have
- Lets callers READ the balance but not write to it
The test file has 9 tests, one per rule. If your code lets even one slip, some caller — hours or years from now — will hit exactly that case in production.