Class Design
What is this?
These problems ask you to model a small real-world thing in code โ a bank, a coffee machine โ where the operations themselves are easy but the organization is the whole game. It is like drawing the floor plan of a building before anyone moves in: where does each responsibility live, and how do the rooms connect? Nobody grades your speed here; they grade whether your design is clean, safe, and easy to extend when a new feature shows up.
๐ก Fun fact: "Validate before you mutate" isn't just interview advice โ it's the backbone of how real banks process transfers. A money move that half-completes is a genuine financial incident, which is why production systems treat the whole operation as all-or-nothing.
๐ The 2 problems in this chapter are free. Sign in with Google or Microsoft to start solving.
Core idea: These problems have no clever algorithm โ the operations are trivial. What's tested is how you model the domain: clean classes, a single source of truth, validating before you mutate, and a design where adding a new case is data, not a rewrite. Get the shape right and the code almost writes itself.
What's actually being graded
Not: "what's the fastest algorithm?"
But: "is this design correct, safe, and extensible?"
- Correctness & safety โ never leave the system in a half-valid state. Validate fully, then mutate. A half-applied transfer is a financial incident.
- Single source of truth โ centralize rules (e.g. one
_valid_accounthelper) instead of copy-pasting checks into every method, where they drift and develop off-by-ones. - Separation of concerns โ split responsibilities so each class has one job; the orchestrator coordinates them.
- Extensibility โ adding a drink / account type / rule should be registering data, not editing core logic.
The two problems
- Design a Bank System โ transfer/deposit/withdraw with invariants (valid account range, sufficient funds). The lesson: centralize validation, validate before mutating, and express
transferas validated withdraw-then-deposit. The "algorithm" is nothing; the discipline is everything. - Design a Coffee Machine โ model Inventory, Recipe (as data), and a Machine that mediates.
dispense= check availability, then deduct atomically. Adding a drink is a new recipe entry, not new branching. This is the open-ended OOP question where the interviewer watches your clarifying questions and your class boundaries.
The design checklist
| Principle | In practice |
|---|---|
| Validate before mutate | check all preconditions, then apply; never partially |
| Single source of truth | one validation/rule method reused everywhere |
| Separation of concerns | distinct classes, each one job; orchestrator wires them |
| Data over code | new cases are registered, not hard-coded |
| Clear API contracts | what does each method return on failure? (False vs raise) |
The insight: in class design the win is legibility and safety. State the invariants, put each rule in exactly one place, and make the common extension (a new account/drink/rule) a one-line data change.
๐ Draw it yourself
- Class diagram. Sketch the Coffee Machine as
Machine โ Inventory + {Recipe}and trace adispensethat checks inventory, then deducts. - Validate-then-mutate. For the Bank, draw a transfer: validate both accounts + funds first, then move money โ show the rejected path leaving balances untouched.
Snap photos and embed them with the /host-diagrams skill.
Key takeaways
- No algorithm โ it's modeling. Clean classes, clear responsibilities, safe mutations.
- Validate before mutating; never leave half-applied state (especially money).
- One source of truth for each rule; don't scatter validation.
- Make extension data, not code โ new drinks/accounts shouldn't touch core logic.
- Why interviewers use these: they reveal whether you can model a domain and write maintainable, safe code โ the day-job skill algorithms don't test.
Order: Design a Bank System โ Design a Coffee Machine.
Design a Bank System
Core idea: The arithmetic is trivial โ the whole problem is validation discipline: check everything in one place, before you touch a single balance, and let
transferreuse the very same checks aswithdrawanddeposit.
This is a pure class-design problem. There's no clever algorithm hiding here โ adding and subtracting numbers is something you learned in grade school. What an interviewer is really watching is whether you can build a small object whose rules live in one place, whose methods don't copy-paste the same guard four times, and whose 1-indexing doesn't quietly produce an off-by-one bug. Get the shape right and the code almost writes itself. Get it wrong and you'll be debugging a negative balance at 4 p.m.
Problem, rephrased
You're building the core ledger for a small credit union. When it opens, it has a fixed roster of member accounts, numbered the way humans number things โ starting at 1 โ each with a starting balance handed to you up front.
You must support three teller operations, and every one of them answers a single yes/no question: "Was this a legal move? If so, I did it. If not, I changed nothing."
deposit(account, money)โ addmoneytoaccount. ReturnsTrueif the account exists, elseFalse.withdraw(account, money)โ removemoneyfromaccount. ReturnsTrueonly if the account exists and has at leastmoney; elseFalse.transfer(from_account, to_account, money)โ movemoneyfrom one account to another. ReturnsTrueonly if both accounts exist and the source has enough; elseFalse.
The non-negotiable invariant: a balance may never go negative, and a rejected operation must leave every balance exactly as it was.
Suppose the credit union opens with 3 accounts: [10, 100, 20] (so account 1 has $10, account 2 has $100, account 3 has $20).
| Operation | Reasoning | Result | Balances after |
|---|---|---|---|
withdraw(3, 10) |
account 3 exists, has $20 โฅ $10 | True |
[10, 100, 10] |
transfer(2, 3, 40) |
both exist, account 2 has $100 โฅ $40 | True |
[10, 60, 50] |
deposit(1, 20) |
account 1 exists | True |
[30, 60, 50] |
transfer(1, 3, 90) |
account 1 only has $30 < $90 | False |
[30, 60, 50] (unchanged) |
withdraw(10, 5) |
account 10 doesn't exist (n = 3) | False |
[30, 60, 50] (unchanged) |
Notice the last two rows: a rejected operation is a no-op. Nothing moved.
Sign in to continue reading
The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.
Sign in to read the full lesson