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.
Tracing a single transaction through the states makes the design concrete. Say a cola costs ₹25, and the customer has a ₹20 and a ₹10 coin:
A3 (cola). IdleState.selectProduct checks stock, records the selection, and moves to HAS_MONEY_PENDING. The display shows "Insert ₹25".Notice what didn't need an if: the same insertCoin call meant "accumulate" in step 2, and would mean "refund immediately" if a coin arrived during step 4. The current state object decides. That's the practical payoff of the pattern: each state class reads like a short description of how the machine behaves in that situation.
1. Money should never be double. 0.1 + 0.2 isn't exactly 0.3 in binary floating point, and a vending machine that compares totals to prices with >= will eventually reject exact payment or return the wrong change. Represent money in the smallest unit, as integers:
enum Coin {
ONE(100), TWO(200), FIVE(500), TEN(1000), TWENTY(2000); // values in paise
final int paise;
Coin(int paise) { this.paise = paise; }
}
final class Product {
final String code; final String name; final int pricePaise;
Product(String code, String name, int pricePaise) { this.code = code; this.name = name; this.pricePaise = pricePaise; }
}
2. "Out of stock" is usually a response, not a state. In the version above, selecting an empty product moves the whole machine into OutOfStockState, which then needs another selection to get out. From a user's point of view, the machine should simply say "A3 is sold out; choose another" and stay IDLE. Keep a machine-wide OutOfService state for cases where the whole machine can't sell (every slot empty, a cash-box fault, or maintenance mode):
class IdleState implements VendingMachineState {
public void selectProduct(VendingMachine m, String code) {
if (!m.inventory().hasStock(code)) {
m.display("Sold out — please choose another item"); // stay IDLE
return;
}
m.startTransaction(code);
m.setState(new HasMoneyPendingState());
}
// ...
}
Customers change their minds. A cancel() event belongs on the state interface:
Because each state implements cancel explicitly, "can the customer cancel now?" has one clear answer per state, rather than a flag checked in several places.
returnChange() hides the trickiest logic in the problem. The machine holds a finite number of each coin, so it may be unable to make change. A greedy approach (take the largest coin that fits, repeatedly) works for standard coin systems when every denomination is available. With limited counts, greedy can fail even when change is possible, so check before accepting payment:
Optional<Map<Coin, Integer>> makeChange(int amount, Map<Coin, Integer> available) {
Map<Coin, Integer> result = new EnumMap<>(Coin.class);
Coin[] coins = Coin.values();
for (int i = coins.length - 1; i >= 0 && amount > 0; i--) { // largest first
Coin c = coins[i];
int use = Math.min(amount / c.paise, available.getOrDefault(c, 0));
if (use > 0) { result.put(c, use); amount -= use * c.paise; }
}
return amount == 0 ? Optional.of(result) : Optional.empty(); // empty → "exact change only"
}
If change can't be made, the machine should refuse the overpaying coin, or show "exact change only", before dispensing, never after taking the money. For guaranteed-optimal change with arbitrary coin sets and limited counts, a small dynamic program (like Coin Change, with counts) replaces the greedy loop without changing any state class.
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.
Q: Why should the vending machine store money as integers rather than doubles?
A: Binary floating point can't represent most decimal amounts exactly, so comparisons such as total >= price and change calculations can be off by tiny amounts, which leads to rejected exact payments or wrong change. Integer minor units (paise, cents) are exact.
Q: Should "out of stock" be a state of the machine?
A: Only if the entire machine can't sell. A single sold-out product is better handled as a rejected selection that leaves the machine IDLE. A machine-level OutOfService state covers cases where every product is gone, or the machine is faulty or in maintenance.
Q: When should the machine check whether it can give change? A: Before completing the sale: ideally when a coin that would overpay is inserted, or at the latest before dispensing. If change isn't possible, refuse the coin or refund, instead of dispensing and keeping the customer's excess money.