Problem 4.2 — Vending Machine
In one line
The classic State-pattern problem: same actions behave differently depending on idle/has-money/dispensing states.
Key ideas
- 01
In plain words: a snack machine. Pressing 'B4' does nothing useful until you've put in money, and does something different again once it's already dispensing. What a button does depends entirely on what state the machine is in right now.
- 02
States: Idle → HasMoney → Dispensing (+ SoldOut, Maintenance in senior answers).
- 03
Actions: insert coin, select item, dispense, refund, reset — each action does something different per state.
- 04
Inventory is a separate concern: item, price, quantity — map<String item, Integer qty>.
- 05
Refund logic: HasMoneyState tracks the inserted amount and returns the right change.
- 06
Reject illegal transitions explicitly (throw or no-op) — interviewers check edge discipline.
- 07
Sold-out handling: item selected but 0 quantity → machine returns to Idle and refunds.
Java / Spring map
- →
Spring StateMachine would be overkill in an interview; hand-rolled State classes are expected.
Code & diagrams
Draw this BEFORE writing any code — every arrow is a method, every box is a class.
State-pattern vending machine with inventory and change.
public class VendingMachine {
private State state = new IdleState();
private int balance = 0;
private final Inventory inventory = new Inventory();
// transitions are only allowed through actions
public void insert(int cents) { state.insert(this, cents); }
public void select(String product) { state.select(this, product); }
public void dispense(String product) { state.dispense(this, product); }
public int refund() { return state.refund(this); }
public void setState(State s) { this.state = s; }
public void add(int c) { balance += c; }
public int balance() { return balance; }
public void addToBalance(int c){ balance += c; }
public void resetBalance() { balance = 0; }
public Inventory inventory() { return inventory; }
}
public class Inventory { // plain map, isolated concern
private final Map<String, Integer> stock = new HashMap<>();
private final Map<String, Integer> price = new HashMap<>();
public void add(String product, int price, int qty) { stock.put(product, qty); this.price.put(product, price); }
public int price(String p) { return price.getOrDefault(p, -1); }
public boolean has(String p){ return stock.getOrDefault(p, 0) > 0; }
public void take(String p) { stock.computeIfPresent(p, (k, v) -> v - 1); }
}
public interface State {
void insert(VendingMachine m, int cents);
void select(VendingMachine m, String product);
void dispense(VendingMachine m, String product);
int refund(VendingMachine m);
}
public class IdleState implements State {
public void insert(VendingMachine m, int c) { m.add(c); m.setState(new HasMoneyState()); }
public void select(VendingMachine m, String p){ throw new IllegalStateException("insert coins first"); }
public void dispense(VendingMachine m, String p){ throw new IllegalStateException("nothing selected"); }
public int refund(VendingMachine m) { throw new IllegalStateException("nothing to refund"); }
}
public class HasMoneyState implements State {
public void insert(VendingMachine m, int c) { m.add(c); }
public void select(VendingMachine m, String p) {
if (!m.inventory().has(p)) throw new IllegalStateException("sold out: " + p);
int price = m.inventory().price(p);
if (m.balance() < price) throw new IllegalStateException("need " + (price - m.balance()) + " more");
m.setState(new DispenseState(p)); // next state carries the product
}
public void dispense(VendingMachine m, String p) { throw new IllegalStateException("select first"); }
public int refund(VendingMachine m) {
int c = m.balance(); m.resetBalance(); m.setState(new IdleState());
return c;
}
}
public class DispenseState implements State {
private final String product;
public DispenseState(String product) { this.product = product; }
public void insert(VendingMachine m, int c) { throw new IllegalStateException("dispensing — wait"); }
public void select(VendingMachine m, String p) { throw new IllegalStateException("dispensing — wait"); }
public void dispense(VendingMachine m, String p) {
int price = m.inventory().price(product);
m.inventory().take(product);
m.addToBalance(-price); // deduct, keep change in machine
System.out.println("dispensed " + product);
m.setState(new IdleState());
}
public int refund(VendingMachine m) { throw new IllegalStateException("cannot refund mid-dispense"); }
}Explain without notes
Why does the State pattern beat a switch-on-state here? Point at the exact if/else you removed.
What happens to a refund when the machine is one coin short of a purchase?
Practice
Add a SoldOutState that auto-refunds, and a MaintenanceState that ignores coins.
Trade-offs
- ↔
One class per state is ceremony; but it makes every illegal transition locally visible.
Completion checklist
I can implement vending machine with the State pattern under 30 minutes.