Command Palette

Search for a command to run...

PHASE 4Intermediate ~10 min· topic 2 of 18Level 1

Problem 4.2 — Vending Machine

In one line

The classic State-pattern problem: same actions behave differently depending on idle/has-money/dispensing states.

0/18 · 0%

Key ideas

  1. 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.

  2. 02

    States: Idle → HasMoney → Dispensing (+ SoldOut, Maintenance in senior answers).

  3. 03

    Actions: insert coin, select item, dispense, refund, reset — each action does something different per state.

  4. 04

    Inventory is a separate concern: item, price, quantity — map<String item, Integer qty>.

  5. 05

    Refund logic: HasMoneyState tracks the inserted amount and returns the right change.

  6. 06

    Reject illegal transitions explicitly (throw or no-op) — interviewers check edge discipline.

  7. 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

VendingStateDiagramdiagram

Draw this BEFORE writing any code — every arrow is a method, every box is a class.

Rendering diagram…
VendingMachine.javajava

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

01

Why does the State pattern beat a switch-on-state here? Point at the exact if/else you removed.

02

What happens to a refund when the machine is one coin short of a purchase?

Practice

01

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.

Back to phase