Difficulty: Advanced
Design a vending machine (or ATM). How would you model its states and behaviours cleanly, and what is the State pattern?
Vending machine and ATM are the second most common LLD warm-ups after parking lot, and they share one key insight: these are state machines. The behaviour of an action depends on the state the machine is in. Pressing the dispense button with no coin means one thing; pressing it after paying means another.
Begin, as always, by clarifying. Which actions? Insert money, select item, dispense, refund, restock. What states? Idle (waiting for money), HasMoney, Dispensing, and maybe OutOfService. What edge cases? Out of stock, insufficient money, exact change, and concurrency (not usually an issue for a single physical machine).
The beginner design puts a state variable in the machine and uses a switch or if-else inside every method: insertCoin checks the state, pressButton checks the state, and so on. It works for two or three states, and the code below starts that way with an enum, which is perfectly acceptable for a warm-up. But as states multiply, every method becomes a tangle of conditions, and adding a new state means editing all the methods, violating the Open/Closed Principle.
The State pattern is the clean generalisation. You create a State interface declaring every action the machine can receive (insertCoin, pressButton, dispense, refund). Each state becomes a class implementing the interface: IdleState, HasMoneyState, DispensingState. The machine keeps a current state reference and delegates every action to it. A state handles the action and, when appropriate, changes the machine's current state to another one. Illegal actions in a state simply print an error or throw. Adding a new state, such as OutOfStockState, means adding a class rather than editing existing ones, and each state class contains only the behaviour that applies to it, giving high cohesion.
Explain the trade-off: for a tiny machine with two states, the enum and switch version is shorter and clearer. The pattern pays off when states and actions grow, or when transitions are complex. It also differs from Strategy, since the states trigger transitions among themselves instead of the client choosing.
For ATM, the same idea applies with states NoCard, HasCard, PinVerified, and Transacting. Other classes you would add: Account and Card entities, a BankService abstraction for balance checks and debits, a CashDispenser that handles denominations (a greedy or chain-of-responsibility approach of 2000, 500, 100 notes is a classic follow-up), and a Transaction record for auditing.
Points to mention in the interview: keep the Inventory (item, price, quantity) as its own class following SRP, make money a proper type (use integer paise or BigDecimal rather than double), guard against invalid transitions, and log every transition for debugging. The enum version below walks through Idle to HasMoney, dispenses, then handles an out-of-stock refund.
class VendingMachine {
enum State { IDLE, HAS_MONEY }
private State state = State.IDLE;
private int stock;
VendingMachine(int stock) { this.stock = stock; }
void insertCoin() {
if (state == State.IDLE) {
state = State.HAS_MONEY;
System.out.println("Coin accepted");
} else {
System.out.println("Already paid");
}
}
void pressButton() {
switch (state) {
case IDLE:
System.out.println("Insert coin first");
break;
case HAS_MONEY:
if (stock == 0) {
System.out.println("Out of stock, refunding");
} else {
stock--;
System.out.println("Dispensing item, left: " + stock);
}
state = State.IDLE;
break;
}
}
}
public class VendingDemo {
public static void main(String[] args) {
VendingMachine vm = new VendingMachine(1);
vm.pressButton();
vm.insertCoin();
vm.insertCoin();
vm.pressButton();
vm.insertCoin();
vm.pressButton();
}
}
interface State {
void insertCoin(Machine m);
void pressButton(Machine m);
}
class Machine {
State state = new Idle();
void insertCoin() { state.insertCoin(this); }
void pressButton() { state.pressButton(this); }
}
class Idle implements State {
public void insertCoin(Machine m) { System.out.println("Coin accepted"); m.state = new HasMoney(); }
public void pressButton(Machine m) { System.out.println("Insert coin first"); }
}
class HasMoney implements State {
public void insertCoin(Machine m) { System.out.println("Already paid"); }
public void pressButton(Machine m) { System.out.println("Dispensing"); m.state = new Idle(); }
}
public class StatePatternDemo {
public static void main(String[] args) {
Machine m = new Machine();
m.pressButton();
m.insertCoin();
m.pressButton();
}
}
LLD, state-pattern, state-machine, vending-machine, design-patterns