What Problem It Solves
An object changes its behavior when its internal state changes. Without State, you get giant if-else chains checking state in every method. With State, each state is a class with its own behavior. Adding a new state means one new class, not modifying every method.
The Problem (Without State)
class VendingMachine {
String state = "IDLE";
void insertCoin() {
if (state.equals("IDLE")) { state = "HAS_MONEY"; }
else if (state.equals("HAS_MONEY")) { /* already has money */ }
else if (state.equals("DISPENSING")) { /* wait */ }
}
void selectItem(String item) {
if (state.equals("IDLE")) { /* no money */ }
else if (state.equals("HAS_MONEY")) { state = "DISPENSING"; }
else if (state.equals("DISPENSING")) { /* already dispensing */ }
}
// Every method needs the same if-else chain.
// Adding "MAINTENANCE" state means editing every method.
}State vs Strategy
| Aspect | State | Strategy |
|---|---|---|
| Purpose | Object behaves differently based on its internal state | Client picks an algorithm |
| Who changes | Context transitions automatically | Client chooses explicitly |
| States share | They know about each other (transitions) | They're independent |
| When to use | Finite state machine (order, vending machine, elevator) | Plugin algorithms (pricing, split, ranking) |
Common Interview Traps
| Trap | Why | Fix |
|---|---|---|
Giant if/else or switch on state | Hard to extend, easy to miss a transition | Extract each state into its own class |
| Forgetting to define all transitions | Some states have invalid transitions that silently do nothing | Every method in every state should handle its transition explicitly (even if it's a no-op) |
| State classes holding context references | Circular dependency | Pass context as method parameter, not constructor dependency |
| Too few states | "IDLE" and "BUSY" aren't enough for complex workflows | List all meaningful states from requirements |
Where State Appears in LLD Problems
| Problem | Context | States |
|---|---|---|
| Vending Machine (#7) | VendingMachine | Idle, HasMoney, Dispensing, SoldOut |
| Elevator (#3) | Elevator | Idle, Moving, DoorOpen, Overloaded, Maintenance |
| Order Management (#68) | Order | Created, Paid, Processing, Shipped, Delivered, Cancelled |
| ATM (#13) | ATMSession | Idle, CardInserted, PinVerified, Transaction, Ejecting |
| Parking Lot (#1) | EntryGate | Available, Occupied, Reserved, OutOfService |
| Chess (#14) | Game | WhiteTurn, BlackTurn, Check, Checkmate, Draw |
Implementation
// โโโ EXAMPLE 1 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// WHAT WE ARE IMPLEMENTING:
// A vending machine controlling item selection and coin insertion
// transitions.
//
// WHERE THE STATE FITS IN:
// VendingMachineState is the State interface. HasCoinState and NoCoinState
// represent Concrete States. VendingMachine acts as the Context tracking
// states.
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// --- State interface ---
interface VendingMachineState {
void insertCoin(VendingMachine vm);
void selectItem(VendingMachine vm, String item);
void dispense(VendingMachine vm);
}
// --- Context ---
class VendingMachine {
private VendingMachineState state;
private int balance = 0;
public VendingMachine() {
state = new IdleState();
}
void setState(VendingMachineState state) {
System.out.println(" [State] " + this.state.getClass().getSimpleName() + " -> " + state.getClass().getSimpleName());
this.state = state;
}
void setBalance(int b) { this.balance = b; }
int getBalance() { return balance; }
public void insertCoin() { state.insertCoin(this); }
public void selectItem(String item) { state.selectItem(this, item); }
public void dispense() { state.dispense(this); }
}
// --- Concrete states ---
class IdleState implements VendingMachineState {
public void insertCoin(VendingMachine vm) {
System.out.println(" Coin inserted: $5");
vm.setBalance(5);
vm.setState(new HasMoneyState());
}
public void selectItem(VendingMachine vm, String item) {
System.out.println(" Insert coin first");
}
public void dispense(VendingMachine vm) {
System.out.println(" Insert coin first");
}
}
class HasMoneyState implements VendingMachineState {
public void insertCoin(VendingMachine vm) {
System.out.println(" Coin already inserted. Balance: $" + vm.getBalance());
}
public void selectItem(VendingMachine vm, String item) {
System.out.println(" Item selected: " + item);
if (vm.getBalance() >= 5) {
vm.setState(new DispensingState());
} else {
System.out.println(" Insufficient balance");
}
}
public void dispense(VendingMachine vm) {
System.out.println(" Select item first");
}
}
class DispensingState implements VendingMachineState {
public void insertCoin(VendingMachine vm) {
System.out.println(" Dispensing in progress");
}
public void selectItem(VendingMachine vm, String item) {
System.out.println(" Dispensing in progress");
}
public void dispense(VendingMachine vm) {
System.out.println(" Dispensing item... Enjoy!");
vm.setBalance(0);
vm.setState(new IdleState());
}
}
public class Main {
public static void main(String[] args) {
VendingMachine vm = new VendingMachine();
// Full flow
vm.insertCoin(); // Idle -> HasMoney
vm.selectItem("Coke"); // HasMoney -> Dispensing
vm.dispense(); // Dispensing -> Idle
// Edge cases
vm.dispense(); // Idle -> "Insert coin first"
vm.selectItem("Chips"); // Idle -> "Insert coin first"
}
}Review
Help Us Improve
How helpful was this walkthrough?
Click a star to rate. We actively use this feedback to refine and update our system design content.
Placeholder
Discussion
Share your thoughts, ask questions, or help others.
Loading comments...