Allow an object to alter its behaviour when its internal state changes. The object will appear to change its class.
The State pattern is a behavioral design pattern that lets an object change its behaviour when its internal state changes. Instead of giant if/else or switch chains, each possible state becomes its own class with its own behaviour — and the object delegates actions to the current state object.
Think of a traffic light: the same "next" action does completely different things depending on whether the light is currently red, yellow, or green. The light doesn't check its own colour with if/else — it delegates to the current state, which knows exactly what to do and what comes next.
The pattern is essentially a finite state machine (FSM) implemented with objects. Every state knows which transitions are valid and what the next state should be — keeping all state logic encapsulated and isolated.
Key Idea: Replace conditional state checks with polymorphism. Each state is a class; the context delegates to the current state object.
The main object whose behaviour changes. It holds a reference to the current state object and delegates all state-dependent operations to it. It also exposes setState() so states can trigger transitions.
Declares all the methods that every state must implement. This ensures the context can call any method on any state without knowing which concrete state it is.
Implements the State interface for one specific state. Each method either performs the appropriate action for that state, or logs an error/does nothing if the action isn't valid in this state. States can also trigger transitions by calling context.setState().
Without State, every method in the context must check the current state and branch accordingly — leading to:
// Conditional explosion — hard to maintain
void pay(Order order) {
if (order.status == PENDING) {
// handle payment
} else if (order.status == PAID) {
// already paid
} else if (order.status == SHIPPED) {
// error
} else if (order.status == CANCELLED) {
// error
}
// Same if/else repeated in ship(), deliver(), cancel()...
}// Each state handles its own logic — no conditionals
class PendingState {
void pay(Order o) { o.setState(new PaidState()); }
void ship(Order o) { System.out.println("Not paid yet"); }
void cancel(Order o) { o.setState(new CancelledState()); }
}
// Add a new state → add a new class, nothing else changes// State Pattern — Vending Machine
// ─── State interface ──────────────────────────────────────────
interface VendingMachineState {
void insertCoin(VendingMachine machine);
void selectProduct(VendingMachine machine, String product);
void dispense(VendingMachine machine);
void cancel(VendingMachine machine);
}
// ─── Context ──────────────────────────────────────────────────
class VendingMachine {
private VendingMachineState state;
private double balance;
private String selectedProduct;
public VendingMachine() {
this.state = new IdleState(); // start in Idle
this.balance = 0;
}
// Delegate all actions to the current state
public void insertCoin(double amount) {
balance += amount;
state.insertCoin(this);
}
public void selectProduct(String product) { state.selectProduct(this, product); }
public void dispense() { state.dispense(this); }
public void cancel() { state.cancel(this); }
// State transitions
public void setState(VendingMachineState state) {
System.out.println("[Machine] State → " + state.getClass().getSimpleName());
this.state = state;
}
public double getBalance() { return balance; }
public void setBalance(double b) { this.balance = b; }
public String getSelected() { return selectedProduct; }
public void setSelected(String p){ this.selectedProduct = p; }
}
// ─── Concrete States ──────────────────────────────────────────
class IdleState implements VendingMachineState {
@Override
public void insertCoin(VendingMachine m) {
System.out.println("[Idle] Coin inserted. Balance: ₹" + m.getBalance());
m.setState(new HasCoinState());
}
@Override public void selectProduct(VendingMachine m, String p) {
System.out.println("[Idle] Please insert a coin first.");
}
@Override public void dispense(VendingMachine m) {
System.out.println("[Idle] No coin inserted.");
}
@Override public void cancel(VendingMachine m) {
System.out.println("[Idle] Nothing to cancel.");
}
}
class HasCoinState implements VendingMachineState {
@Override
public void insertCoin(VendingMachine m) {
System.out.println("[HasCoin] Additional coin. Balance: ₹" + m.getBalance());
}
@Override
public void selectProduct(VendingMachine m, String product) {
m.setSelected(product);
System.out.println("[HasCoin] Selected: " + product);
m.setState(new ProductSelectedState());
}
@Override public void dispense(VendingMachine m) {
System.out.println("[HasCoin] Please select a product first.");
}
@Override
public void cancel(VendingMachine m) {
System.out.println("[HasCoin] Cancelled. Returning ₹" + m.getBalance());
m.setBalance(0);
m.setState(new IdleState());
}
}
class ProductSelectedState implements VendingMachineState {
@Override public void insertCoin(VendingMachine m) {
System.out.println("[Selected] Product already selected. Press dispense.");
}
@Override public void selectProduct(VendingMachine m, String p) {
System.out.println("[Selected] Already selected: " + m.getSelected());
}
@Override
public void dispense(VendingMachine m) {
System.out.println("[Selected] Dispensing: " + m.getSelected());
m.setBalance(0);
m.setState(new IdleState());
}
@Override
public void cancel(VendingMachine m) {
System.out.println("[Selected] Cancelled. Returning ₹" + m.getBalance());
m.setBalance(0);
m.setState(new IdleState());
}
}
// ─── Client ───────────────────────────────────────────────────
public class Application {
public static void main(String[] args) {
VendingMachine machine = new VendingMachine();
machine.selectProduct("Chips"); // Idle: insert coin first
machine.insertCoin(20); // → HasCoin state
machine.insertCoin(10); // add more
machine.selectProduct("Chips"); // → ProductSelected state
machine.dispense(); // → back to Idle
machine.cancel(); // Idle: nothing to cancel
}
}Any entity that moves through defined stages — pending, paid, shipped, delivered, cancelled.
OrderState, InvoiceState, TicketStateButtons, toggles, forms that behave differently depending on their current mode — idle, loading, error, success.
ButtonState, FormState, PlayerStateGame entities that change behaviour based on health, mode, or environment — idle, patrolling, attacking, fleeing.
EnemyIdleState, AttackState, FleeStateConnections that move through connecting, connected, reconnecting, disconnected states with different behaviours in each.
ConnectingState, ConnectedState, DisconnectedStatePhysical devices with a strict sequence of states — idle, has coin, product selected, dispensing.
IdleState, HasCoinState, DispensingState| Aspect | State | Strategy |
|---|---|---|
| Intent | Object changes behaviour as its state evolves | Client selects an algorithm to use |
| Transitions | States trigger each other (state machine) | Client chooses the strategy explicitly |
| States aware of each other? | Yes — states know about valid transitions | No — strategies are independent |
| Context knowledge | States reference the context to trigger transitions | Strategies typically don't need context reference |
React manages UI state transitions — loading, success, error — using state variables. Libraries like XState bring the full State pattern to frontend, defining machines with explicit states and transitions.
TCP implements a classic state machine: LISTEN, SYN_SENT, ESTABLISHED, FIN_WAIT, CLOSED. Each state handles the same network events differently, following the State pattern exactly.
ATM software is a textbook State pattern implementation — idle, card inserted, PIN entered, transaction, dispensing cash. Each physical state maps to a concrete state class.
Spring Statemachine and tools like Activiti/Camunda implement workflow engines using the State pattern — each workflow node is a state, transitions are triggered by events.