Skip to content
System design course
Ch.4 · Designing real systems·concept ·7 min read

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.