Design vending machine
The cleanest State machine you'll be asked to build — and the refund path is the trap.
warm-up · 45 minutes · 8 classes
Requirements
- Accept coins and notes of known denominations, accumulating a balance.
- Select a product; dispense only if it's in stock and the balance covers it.
- Return change, and refuse the sale if exact change can't be made.
- Cancel at any point before dispensing and refund everything inserted.
- Restock and float-collection operations for an operator.
Say these are out of scope
- Card and contactless payment.
- Remote telemetry and stock alerts.
- Physical jam detection.
A shape that works
One reasonable decomposition, not the only one. What matters in the round is that you can defend the boundaries you drew.
What they'll push on
Why State here rather than a status enum and a few conditionals?
Because every operation is state-dependent — inserting a coin means something different mid-transaction than while dispensing — so the enum version puts the same switch at the top of every method. With states as classes, DispensingState.insert() simply refuses, and adding a maintenance mode adds a class instead of a branch in four places.
The customer paid and the product jams. What now?
This is the trap. Dispensing is two effects — reduce stock, release change — and a failure between them either loses the customer's money or gives away stock. Reserve the item first, dispense, then commit; on failure release the reservation and refund. Saying "I'd make it transactional" without naming the two effects is the answer that gets probed.
Balance covers the price but you can't make exact change.
Refuse before dispensing and say so. It's a real vending-machine behaviour and it forces the change calculation to happen before the point of no return — which is also why canMakeChange is a separate query from dispense.