</> MAANG.io
coding interview ยท 101

Foundations

Master coding interviews with comprehensive coverage of data structures, algorithms, and problem-solving techniques. Progress from fundamentals to advanced topics with expertly curated content.

0/255 solved 0% complete

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.

flowchart TD A["Model the domain as classes"] --> B["Give each class one clear job"] B --> C["Validate fully before mutating anything"] C --> D["Add new cases as data not new code"]

๐Ÿ’ก 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_account helper) 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

Class design splits into two practice problems: Design a Bank System (validate-before-mutate) and Design a Coffee Machine (separate inventory / recipes / machine)

  • Design a Bank System โ€” transfer/deposit/withdraw with invariants (valid account range, sufficient funds). The lesson: centralize validation, validate before mutating, and express transfer as 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

  1. Class diagram. Sketch the Coffee Machine as Machine โ†’ Inventory + {Recipe} and trace a dispense that checks inventory, then deducts.
  2. 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 Coffee Machine

Core idea: Don't write one giant dispense() that hardcodes every drink. Split the problem into three actors โ€” an Inventory that owns quantities, Recipes that are pure data (a name + how much of each ingredient it costs), and a Machine that mediates: check availability, then deduct atomically. Adding a new drink becomes data you register, not code you edit.


1. The problem, in a fresh setting

You're building the firmware brain for an office coffee bar. Behind the front panel are tanks of water, milk, and beans (and tomorrow, maybe chocolate and oat milk). The kitchen menu is a small set of drinks, each of which is really just a bill of materials: an espresso is "30 ml water + 18 g beans"; a latte is "30 ml water + 18 g beans + 150 ml milk."

Your controller must support exactly three things:

Operation Meaning
dispense(drink) If every required ingredient is in stock, deduct them and serve the drink. Otherwise, serve nothing and report which ingredient is short.
refill(ingredient, amt) Top a tank back up by amt (without overflowing its capacity).
low_ingredients() List ingredients that have dropped below a "reorder soon" threshold.

This is an open-ended design problem. There is no clever algorithm hiding here โ€” the interviewer is watching how you model it. Can a new drink be added without touching dispense? Are availability-check and deduction kept honest (no half-charged brews)? Is each piece testable on its own?

A small usage walk-through (tanks start at water=500 ml, milk=200 ml, beans=100 g):

Call Result Tanks after
dispense("espresso") โœ… served espresso water=470, milk=200, beans=82
dispense("latte") โŒ blocked โ€” short on milk water=470, milk=200, beans=82 (unchanged)
refill("milk", 300) milk topped up water=470, milk=500*, beans=82
dispense("latte") โœ… served latte water=440, milk=350, beans=64

* clamped to the milk tank's capacity (we'll set capacity = 500 ml).

Notice the key invariant in row 2: a blocked dispense changes nothing. We don't pour the water and then discover the milk is empty.


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

Sign in to MAANG.io

Use your Google or Microsoft account โ€” no password to remember.

Continue with Google Continue with Microsoft

Please accept the terms above to continue.