The State pattern lets an object change its behavior when its internal state changes. From the outside, it looks like the object changed its class entirely.
Think of it like a vending machine. When it’s idle, pressing buttons does nothing useful. Insert money, and now the buttons actually work. Select an item, and it dispenses. Try to select again without money? Nope, back to idle. Same machine, completely different behavior depending on its state.
The Problem
Without the State pattern, we end up with code like this:
def press_button(self):
if self.state == "idle":
print("Insert money first")
elif self.state == "has_money":
self.dispense()
self.state = "dispensing"
elif self.state == "dispensing":
print("Already dispensing, please wait")
elif self.state == "out_of_stock":
print("Sorry, out of stock")
Every method has the same if-else chain. Add a new state? We have to update every single method. Forget one? Bug. This gets out of hand fast.
How State Fixes This
- set_state(state)
- insert_money() / select_item()
+ select_item(context)
+ dispense(context)
Each state is its own class. The context delegates all behavior to the current state object. When a transition happens, the state object swaps itself out for the next one. No if-else chains anywhere.
Implementation — Vending Machine
from abc import ABC, abstractmethod
class State(ABC):
@abstractmethod
def insert_money(self, machine) -> None:
pass
@abstractmethod
def select_item(self, machine) -> None:
pass
@abstractmethod
def dispense(self, machine) -> None:
pass
class IdleState(State):
def insert_money(self, machine):
print("Money accepted!")
machine.set_state(HasMoneyState())
def select_item(self, machine):
print("Insert money first.")
def dispense(self, machine):
print("Insert money and select an item first.")
class HasMoneyState(State):
def insert_money(self, machine):
print("Money already inserted.")
def select_item(self, machine):
if machine.stock > 0:
print("Item selected. Dispensing...")
machine.set_state(DispensingState())
else:
print("Out of stock! Refunding money.")
machine.set_state(OutOfStockState())
def dispense(self, machine):
print("Select an item first.")
class DispensingState(State):
def insert_money(self, machine):
print("Please wait, dispensing in progress.")
def select_item(self, machine):
print("Already dispensing, please wait.")
def dispense(self, machine):
machine.stock -= 1
print(f"Item dispensed! ({machine.stock} left)")
if machine.stock > 0:
machine.set_state(IdleState())
else:
machine.set_state(OutOfStockState())
class OutOfStockState(State):
def insert_money(self, machine):
print("Sorry, machine is out of stock.")
def select_item(self, machine):
print("Machine is out of stock.")
def dispense(self, machine):
print("Nothing to dispense.")
class VendingMachine:
def __init__(self, stock: int):
self.stock = stock
self._state: State = IdleState() if stock > 0 else OutOfStockState()
def set_state(self, state: State):
self._state = state
def insert_money(self):
self._state.insert_money(self)
def select_item(self):
self._state.select_item(self)
def dispense(self):
self._state.dispense(self)
# Usage
machine = VendingMachine(2)
machine.select_item() # Insert money first.
machine.insert_money() # Money accepted!
machine.select_item() # Item selected. Dispensing...
machine.dispense() # Item dispensed! (1 left)
machine.insert_money() # Money accepted!
machine.select_item() # Item selected. Dispensing...
machine.dispense() # Item dispensed! (0 left)
machine.insert_money() # Sorry, machine is out of stock.
class IdleState {
insertMoney(machine) {
console.log("Money accepted!");
machine.setState(new HasMoneyState());
}
selectItem(machine) {
console.log("Insert money first.");
}
dispense(machine) {
console.log("Insert money and select an item first.");
}
}
class HasMoneyState {
insertMoney(machine) {
console.log("Money already inserted.");
}
selectItem(machine) {
if (machine.stock > 0) {
console.log("Item selected. Dispensing...");
machine.setState(new DispensingState());
} else {
console.log("Out of stock! Refunding money.");
machine.setState(new OutOfStockState());
}
}
dispense(machine) {
console.log("Select an item first.");
}
}
class DispensingState {
insertMoney(machine) {
console.log("Please wait, dispensing in progress.");
}
selectItem(machine) {
console.log("Already dispensing, please wait.");
}
dispense(machine) {
machine.stock--;
console.log(`Item dispensed! (${machine.stock} left)`);
machine.setState(machine.stock > 0 ? new IdleState() : new OutOfStockState());
}
}
class OutOfStockState {
insertMoney(machine) { console.log("Sorry, machine is out of stock."); }
selectItem(machine) { console.log("Machine is out of stock."); }
dispense(machine) { console.log("Nothing to dispense."); }
}
class VendingMachine {
constructor(stock) {
this.stock = stock;
this.state = stock > 0 ? new IdleState() : new OutOfStockState();
}
setState(state) { this.state = state; }
insertMoney() { this.state.insertMoney(this); }
selectItem() { this.state.selectItem(this); }
dispense() { this.state.dispense(this); }
}
// Usage
const machine = new VendingMachine(2);
machine.selectItem(); // Insert money first.
machine.insertMoney(); // Money accepted!
machine.selectItem(); // Item selected. Dispensing...
machine.dispense(); // Item dispensed! (1 left)
interface State {
void insertMoney(VendingMachine machine);
void selectItem(VendingMachine machine);
void dispense(VendingMachine machine);
}
class IdleState implements State {
public void insertMoney(VendingMachine m) {
System.out.println("Money accepted!");
m.setState(new HasMoneyState());
}
public void selectItem(VendingMachine m) {
System.out.println("Insert money first.");
}
public void dispense(VendingMachine m) {
System.out.println("Insert money and select an item first.");
}
}
class HasMoneyState implements State {
public void insertMoney(VendingMachine m) {
System.out.println("Money already inserted.");
}
public void selectItem(VendingMachine m) {
if (m.getStock() > 0) {
System.out.println("Item selected. Dispensing...");
m.setState(new DispensingState());
} else {
System.out.println("Out of stock! Refunding.");
m.setState(new OutOfStockState());
}
}
public void dispense(VendingMachine m) {
System.out.println("Select an item first.");
}
}
class DispensingState implements State {
public void insertMoney(VendingMachine m) {
System.out.println("Please wait, dispensing.");
}
public void selectItem(VendingMachine m) {
System.out.println("Already dispensing, please wait.");
}
public void dispense(VendingMachine m) {
m.decrementStock();
System.out.println("Item dispensed! (" + m.getStock() + " left)");
m.setState(m.getStock() > 0 ? new IdleState() : new OutOfStockState());
}
}
class OutOfStockState implements State {
public void insertMoney(VendingMachine m) { System.out.println("Out of stock."); }
public void selectItem(VendingMachine m) { System.out.println("Out of stock."); }
public void dispense(VendingMachine m) { System.out.println("Nothing to dispense."); }
}
class VendingMachine {
private State state;
private int stock;
public VendingMachine(int stock) {
this.stock = stock;
this.state = stock > 0 ? new IdleState() : new OutOfStockState();
}
public void setState(State s) { this.state = s; }
public int getStock() { return stock; }
public void decrementStock() { stock--; }
public void insertMoney() { state.insertMoney(this); }
public void selectItem() { state.selectItem(this); }
public void dispense() { state.dispense(this); }
}
State vs Strategy — The Interview Favorite
This is one of the most commonly asked comparisons in LLD interviews.
| State | Strategy | |
|---|---|---|
| Who controls switching? | States transition themselves | Client picks the strategy |
| States aware of each other? | Yes, each state knows the next | No, strategies are independent |
| Purpose | Model object lifecycle | Swap algorithms |
| Example | Order: Placed → Shipped → Delivered | Payment: Card, PayPal, Crypto |
The only difference is intent. State models transitions in an object’s lifecycle. Strategy lets us swap interchangeable algorithms. Structurally, they look almost identical.
When to Use
- Object lifecycle — order states, document workflow, game character modes
- Finite state machines — traffic lights, network protocols, vending machines
- Complex conditional behavior — when methods have large state-dependent if-else blocks
- UI component states — loading, error, success, empty
When NOT to Use
- When we only have 2-3 states with simple logic — a simple enum and switch is fine
- When states don’t have meaningfully different behavior — just use a status field
- When transitions are rare and simple — the pattern adds overhead for little benefit
In simple language, the State pattern says “don’t ask what state we’re in — just let each state handle things its own way.” Instead of checking if state == X everywhere, we let the state object do the talking. New state? New class. No existing code touched. Clean.