Designing a vending machine
The textbook State-pattern problem — model a machine that behaves differently depending on whether money is inserted, a product is selected, or it's sold out.
Why this problem is about state
A vending machine is the canonical State pattern interview. Its defining trait:
the same action does different things depending on the current state. Pressing
“select product” with no money inserted should reject; with money inserted it
should dispense. Model that cleanly and you’ve nailed it; model it with a tangle of
if flags and you’ve failed it.
Step 1 — Requirements
Functional:
- Hold products in slots, each with a price and a quantity.
- Accept coins/notes (and track inserted balance).
- Select a product; if enough money and in stock, dispense it and return change.
- Allow cancel (refund inserted money).
- An operator can restock and collect money.
Non-functional: correctness (never dispense without payment, never overcharge), extensibility (new payment methods, new products), and handle the awkward states (sold out, insufficient funds, exact-change-only).
Step 2 — Entities
- VendingMachine — the context; holds inventory, current balance, and the current state.
- State (interface) → IdleState / NoMoneyState, HasMoneyState, DispenseState, SoldOutState — each implements the same actions differently.
- Product / Slot (Inventory) — item, price, count.
- Coin / Note (enums for denominations).
- Inventory — products + a coin reserve for making change.
Step 3 — The state machine (the crux)
The actions are: insertMoney, selectProduct, dispense, cancel. Draw the
transitions:
NoMoney ──insert──▶ HasMoney ──select(ok)──▶ Dispense ──▶ (return change) ──▶ NoMoney
▲ │ └──select(insufficient)──▶ HasMoney (ask for more)
│ └──cancel──▶ NoMoney (refund)
SoldOut ◀── any state when the chosen slot hits 0
Each State class handles every action — and either performs it or rejects it. That’s the State pattern: behavior varies by state, and states decide the next state, with no giant conditional in the machine itself.
Step 4 — Why State beats a flag soup
The naive design keeps booleans (hasMoney, isDispensing) and branches on them in
every method — which explodes combinatorially and breaks as you add cases. The
State pattern encapsulates each situation’s behavior in its own class, so:
- adding a state (e.g. “maintenance”) doesn’t touch the others,
- illegal actions are rejected locally (“can’t dispense in NoMoneyState”),
- the machine just delegates to
current_state.action().
Naming this trade-off — “I’ll use the State pattern so behavior lives with each state instead of in nested conditionals” — is exactly the signal.
Step 5 — Other patterns to mention
- Strategy — pluggable payment methods (coins, card, mobile) or change-making algorithms.
- Factory — create state objects.
- Singleton — one machine instance.
- Observer — notify an operator/display when a slot is low or money is full.
The interview cue
Lead with: “This is a state machine — I’ll model NoMoney / HasMoney / Dispense / SoldOut as State classes, each implementing insert/select/dispense/cancel, so the same action behaves correctly per state without conditional spaghetti.” Then move to implementing the states and the change-making logic — the next lesson. Recognizing the State pattern up front is what this problem is testing.