Free preview

Why this matters: the vending machine is the canon's purest state-machine problem, and interviewers use it to see one thing above all — whether your states are structure or accident. A machine whose behavior emerges from scattered booleans (hasMoney, isDispensing, cancelled) works right up until two flags disagree. A machine whose states are explicit can be read, tested, and — crucially — extended one transition at a time.

The model in words

Say it before you draw it: "The machine is an explicit state machine — idle, collecting, dispensing — where every hardware event is handled according to the current state, and illegal event/state pairs are rejected loudly rather than absorbed silently. Money lives in one ledger object; change-making is its own component because its correctness is subtler than it looks; inventory is bookkeeping the state machine consults but doesn't own."

That's four ownership decisions. Here is each one earning its place.

States as first-class structure

Three states carry the machine, with transitions only where the requirements put them:

            coin inserted
   IDLE  ------------------>  COLLECTING
     ^                          |   |
     |   cancel / sold-out /    |   | selection pressed,
     |   cannot-make-change     |   | balance sufficient,
     |   (refund inserted)      |   | change makeable
     +--------------------------+   v
     ^                          DISPENSING
     |     product + change         |
     +------------------------------+

The design choice with teeth is how states get represented. Two honest options: a state enum with a transition table (compact, all transitions visible in one place) or the State pattern — one object per state, each handling the events legal for it. For three states either is defensible; what matters is the justification. The State pattern earns its keep the moment per-state behavior grows (collecting handles coins and selection and cancel; idle handles only coins) — each state object owns exactly its own legal responses, and an event arriving in the wrong state has nowhere to hide. That's single responsibility applied to behavior: each state answers only for itself.

Whichever representation you pick, the rule to state out loud: transition logic exists in exactly one layer. Not smeared across event handlers, not duplicated in the display code.

Money: one ledger, integer units

Money in this machine has three distinct piles, and confusing them is the classic bookkeeping bug: the inserted balance (this purchase's coins, refundable until dispense), the change float (the machine's stock of denominations for making change), and the cash box (collected revenue). Model them separately. A cancelled purchase refunds the inserted balance; a completed one moves it into the cash box; change comes out of the float.

All three are integer counts of fixed denominations — never floating-point currency. The inserted balance isn't just an amount, it's which coins — because on cancel, the machine returns coins, and it can only return coins it can dispense.

Change-making: a small component with a real correctness question

"Return 13 in change" looks trivial — greedily take the largest denomination that fits, repeat. Here is the sentence that earns real points in the round: greedy change-making is only correct for some denomination sets. For canonical sets (1, 2, 5, 10, 20) greedy is optimal. For a set like (1, 3, 4), greedy on 6 gives 4+1+1 — three coins where two 3s would do; worse, with no 1s in the float, greedy can fail where a solution exists. Since denominations are configuration, the change-maker's algorithm is a real decision, not a formality — greedy with the canonical set today, with the limitation named, and a dynamic-programming fallback as the known upgrade if the config ever goes exotic.

This is why change-making is its own component rather than three lines in the dispense flow: it has an algorithm, a correctness argument, and a dependency on the float's current contents. And it answers the pre-dispense question from lesson 01 — can we make change for this sale? — which the state machine must ask before entering dispensing, because our machine refuses rather than shortchanges.

Inventory: consulted, not owned

Stock counts per product slot: decremented on dispense, blocking selection at zero. The temptation is to weave stock checks through the transition logic; the cleaner shape is an inventory object the collecting state consults — "is this selection available?" — keeping restocking (the operator's future feature) a change to one component rather than to the state machine. Separation of concerns, placed where the next requirement will land.

Invariants worth stating

- the machine is in exactly one state; every event is handled or
  rejected according to that state alone
- inserted balance is refundable in full until dispensing begins
- dispense happens only when: stock > 0 AND balance >= price AND
  change for (balance - price) is makeable from the float
- money never leaves the three ledgers except by dispense/refund/collect

That third invariant is the design's heart — all three conditions checked before the point of no return.

What we rejected

Boolean soup. hasEnoughMoney, productSelected, isCancelling — every flag combination is an implicit state, most of them meaningless, and the bugs live in the meaningless ones. The explicit machine exists precisely to make illegal states unrepresentable.

Change logic inline in dispense. Then the pre-dispense feasibility check and the actual coin selection drift apart, and one day they disagree — the machine promises change it can't make. One component, asked twice (feasibility, then execution), cannot disagree with itself.

Key takeaway

The vending machine is an explicit state machine — idle, collecting, dispensing — with every event handled according to the current state and transition logic living in exactly one layer. Money is three separate integer ledgers (inserted, float, cash box); change-making is its own component because greedy is only correct for some denomination sets and feasibility must be known before dispensing; inventory is consulted, not owned. The dispense invariant — stock, balance, and makeable change, all checked before the point of no return — is the design in one sentence.

Enjoying the preview?

Create a free account to unlock the rest of this course, the in-browser judge, and live AI mock interviews.

Sign up free to continue