The canonical State pattern interview question — modeling a vending machine as Idle/HasMoney/Dispensing/OutOfStock states, explicitly.
Published September 23, 2026
If one machine-coding prompt exists specifically to test State Pattern, it's this one — a vending machine's behavior is entirely determined by which state it's in, making it close to the cleanest real-world example of the pattern.
class Product {
final String code;
final double price;
int quantity;
}
class Inventory {
private final Map<String, Product> products = new HashMap<>();
boolean hasStock(String code) { return products.containsKey(code) && products.get(code).quantity > 0; }
void dispense(String code) { products.get(code).quantity--; }
}
interface CoinAcceptor { double insertCoin(Coin coin); } // returns running total inserted
IDLE ──(product selected)──▶ HAS_MONEY_PENDING (waiting for sufficient payment)
HAS_MONEY_PENDING ──(enough money inserted)──▶ DISPENSING
DISPENSING ──(product released)──▶ IDLE (or OUT_OF_STOCK if that was the last unit)
any state ──(selected product has zero stock)──▶ OUT_OF_STOCK (rejects selection, returns to IDLE)
interface VendingMachineState {
void selectProduct(VendingMachine machine, String code);
void insertCoin(VendingMachine machine, Coin coin);
void dispense(VendingMachine machine);
}
class IdleState implements VendingMachineState {
public void selectProduct(VendingMachine machine, String code) {
if (!machine.getInventory().hasStock(code)) {
machine.setState(new OutOfStockState());
return;
}
machine.setSelectedProduct(code);
machine.setState(new HasMoneyPendingState());
}
public void insertCoin(VendingMachine machine, Coin coin) { /* no-op or reject — no product selected yet */ }
public void dispense(VendingMachine machine) { /* no-op — nothing to dispense */ }
}
class HasMoneyPendingState implements VendingMachineState {
public void selectProduct(VendingMachine machine, String code) { /* already mid-transaction — ignore or reject */ }
public void insertCoin(VendingMachine machine, Coin coin) {
double total = machine.addPayment(coin.getValue());
if (total >= machine.getSelectedProductPrice()) {
machine.setState(new DispensingState());
}
}
public void dispense(VendingMachine machine) { /* not ready yet */ }
}
class DispensingState implements VendingMachineState {
public void selectProduct(VendingMachine machine, String code) { /* mid-dispense — ignore */ }
public void insertCoin(VendingMachine machine, Coin coin) { machine.refund(coin); } // reject further coins mid-dispense
public void dispense(VendingMachine machine) {
machine.getInventory().dispense(machine.getSelectedProduct());
machine.returnChange();
machine.setState(machine.getInventory().hasStock(machine.getSelectedProduct()) ? new IdleState() : new OutOfStockState());
}
}
class OutOfStockState implements VendingMachineState {
public void selectProduct(VendingMachine machine, String code) {
if (machine.getInventory().hasStock(code)) machine.setState(new IdleState()); // a DIFFERENT product might still be in stock
}
public void insertCoin(VendingMachine machine, Coin coin) { machine.refund(coin); }
public void dispense(VendingMachine machine) { /* nothing to dispense */ }
}
class VendingMachine {
private VendingMachineState state = new IdleState();
void setState(VendingMachineState state) { this.state = state; }
void selectProduct(String code) { state.selectProduct(this, code); } // every public method delegates identically
void insertCoin(Coin coin) { state.insertCoin(this, coin); }
void dispenseProduct() { state.dispense(this); }
}
The reason this is the canonical State pattern question, rather than just an example: every one of VendingMachine's public methods has genuinely different, non-trivial behavior in every state (inserting a coin does nothing in IdleState, accumulates toward payment in HasMoneyPendingState, and triggers a refund in DispensingState/OutOfStockState) — this is exactly the profile State Pattern's own "when it earns its complexity" guidance describes, unlike the traffic-light example where an enum+switch would have been simpler.
Q: Why does OutOfStockState.selectProduct() check hasStock() for the newly-selected code, rather than just staying OutOfStock forever? A: Out-of-stock is per-product, not a global machine condition — a machine that's out of one item can still sell a different item, so the state needs to re-evaluate stock for whatever the new selection is, not assume the machine-wide state should persist.
Q: How would you handle a coin insertion that overshoots the product price? A: DispensingState (or a transition trigger just before it) needs to compute and dispense change — machine.returnChange() in the dispense() implementation is where that logic would live, calculating (total inserted - product price) and dispensing the appropriate coins/refusing if exact change isn't available.
Q: What's a concurrency concern for a real vending machine handling this design? A: A physical vending machine typically serves one transaction at a time by hardware nature, so concurrency is less of an issue than, say, Parking Lot's concurrent spot claims — but a software simulation or a networked/cashless variant would need the same kind of per-transaction locking discussed in Parking Lot — Implementation if multiple insertCoin/selectProduct calls could race.
Q: Could this same state machine structure model a different real-world system? A: Yes — any system where behavior is genuinely gated by a multi-step transaction with distinct in-progress states (a self-checkout kiosk, an elevator's door-open/moving states from Elevator System, an order's Pending/Paid/Shipped lifecycle) follows the identical State pattern shape.