Behavioral

State

Allow an object to alter its behaviour when its internal state changes — the object will appear to change its class.

Intermediate Pattern #20 of 23

What is the State Pattern?

State allows an object (Context) to change its behaviour when its internal state changes. Instead of giant if-else or switch blocks checking state, each state is encapsulated in its own class. The Context delegates all state-dependent behaviour to the current State object.

From the client's perspective the Context behaves differently at different times — as if it changed its class — but the Context class itself never changes.

Real-world analogy: A vending machine. In "Idle" state: inserting money transitions to "Has Money". In "Has Money": pressing a button transitions to "Dispensing". In "Dispensing": completing dispense returns to "Idle". Same machine, different behaviours per state.

The Problem It Solves

You're modelling a traffic light. It has states: Red, Yellow, Green. In each state, next() transitions to a different state and getSignal() returns a different string. With if-else: one massive method checking the current state for every operation. Add a new state and every method needs updating.

State pattern puts Red, Yellow, Green each in their own class. Adding a new state is adding one new class — zero changes to existing states or the Context.

Participants

Context
Maintains a reference to the current State. Exposes the interface clients use. Delegates all state-dependent calls to the current State object. May allow states to change the context's current state.
State (interface)
Declares all state-dependent methods. Each ConcreteState implements these for its specific behaviour.
ConcreteState
Implements behaviour for a specific state. Has a back-reference to Context to trigger state transitions. Each state knows which state to transition to.

Visual Flow Diagram

Context state: State setState(s) request() → state.handle() «interface» State handle(context) RedState handle() → → GreenState GreenState handle() → → YellowState YellowState handle() → → RedState Each state knows next state → calls context.setState(next)

Java Code Example

Java — Vending Machine State Machine
// State interface
public interface VendingState {
    void insertCoin(VendingMachine ctx);
    void pressButton(VendingMachine ctx);
    void dispense(VendingMachine ctx);
}

// Context
public class VendingMachine {
    private VendingState state;
    private int           stock;

    public VendingMachine(int stock) {
        this.stock = stock;
        state = new IdleState(); // initial state
    }

    public void setState(VendingState s)  { state = s; }
    public int  getStock()                 { return stock; }
    public void decrementStock()          { stock--; }

    // Delegates all behaviour to current state
    public void insertCoin()  { state.insertCoin(this); }
    public void pressButton() { state.pressButton(this); }
    public void dispense()    { state.dispense(this); }
}

// Concrete States
public class IdleState implements VendingState {
    public void insertCoin(VendingMachine ctx) {
        System.out.println("Coin inserted");
        ctx.setState(new HasMoneyState()); // transition
    }
    public void pressButton(VendingMachine ctx) {
        System.out.println("Insert coin first");
    }
    public void dispense(VendingMachine ctx) {
        System.out.println("No coin inserted");
    }
}

public class HasMoneyState implements VendingState {
    public void insertCoin(VendingMachine ctx) {
        System.out.println("Already has coin");
    }
    public void pressButton(VendingMachine ctx) {
        if (ctx.getStock() > 0) {
            System.out.println("Dispensing...");
            ctx.setState(new DispensingState());
        } else {
            System.out.println("Out of stock — returning coin");
            ctx.setState(new IdleState());
        }
    }
    public void dispense(VendingMachine ctx) {
        System.out.println("Press button first");
    }
}

public class DispensingState implements VendingState {
    public void insertCoin(VendingMachine ctx)  { System.out.println("Please wait"); }
    public void pressButton(VendingMachine ctx) { System.out.println("Already dispensing"); }
    public void dispense(VendingMachine ctx) {
        System.out.println("Item dispensed!");
        ctx.decrementStock();
        ctx.setState(ctx.getStock() > 0
            ? new IdleState()
            : new OutOfStockState());
    }
}

// Client
public class Main {
    public static void main(String[] args) {
        VendingMachine vm = new VendingMachine(2);
        vm.pressButton(); // Insert coin first
        vm.insertCoin();  // Coin inserted
        vm.pressButton(); // Dispensing...
        vm.dispense();    // Item dispensed!
    }
}

When to Use / Avoid

✓ Use When

  • An object's behaviour depends on its state and must change at runtime
  • Operations have large multi-part conditionals that depend on state
  • States and transitions are explicit and need to be documented/tested independently
  • Finite state machines: order processing, workflow, UI components, protocol parsers

✕ Avoid When

  • Only a few states with simple transitions — a simple enum + switch is cleaner
  • States change rarely — the abstraction cost isn't worth it
  • You need formal FSM verification — use a proper state machine library

Real-World Examples

Pros & Cons

Pros

  • Eliminates complex conditionals — each state is self-contained
  • Open/Closed — add new states without changing Context or other states
  • Single Responsibility — each state class handles only its behaviour
  • State transitions are explicit and easy to test in isolation

Cons

  • Class proliferation — many states = many classes
  • States can be tightly coupled if they directly reference each other for transitions
  • Overkill for objects with only 2-3 trivially simple states

How State Can Be Broken

⚠ Attack Vectors

  • Context directly checks state type: if (state instanceof RedState) in Context methods — reintroduces the type-checking if-else that State was designed to eliminate
  • States referencing each other directly: RedState imports and instantiates GreenState — states are tightly coupled, making it impossible to add/change states without cascade edits
  • Missing state transition: A ConcreteState doesn't implement all State methods and leaves some as empty no-ops — invalid actions in a state are silently ignored rather than rejected
  • Shared mutable state objects: The same ConcreteState instance is shared across multiple Context objects — one context's action changes the shared state's fields, corrupting the other context

✓ Prevention

  • Never instanceof in Context: Context only calls state.handle(ctx) — it has zero knowledge of which specific state it holds
  • Use state IDs or factory for transitions: Instead of states instantiating each other directly, transitions go through a factory or pass a string/enum to Context, which resolves the next state — keeps states decoupled
  • Throw on invalid operations: In State methods that aren't valid for a given state, throw IllegalStateException — fail fast and loudly rather than silent no-op
  • One instance per context: Create a fresh ConcreteState per Context (or use stateless Singletons only if states have no instance fields) — never share mutable state objects
Java — Break & Fix
// ❌ BREAKING — instanceof in Context defeats the pattern
public void pressButton() {
    if (state instanceof IdleState) {       // ❌ back to if-else hell
        System.out.println("Insert coin first");
    } else if (state instanceof HasMoneyState) {
        System.out.println("Dispensing");
    }
}

// ✅ FIX — delegate to state, no instanceof
public void pressButton() {
    state.pressButton(this); // ✅ polymorphic dispatch — Context knows nothing
}

// ❌ BREAKING — state creates next state directly (tight coupling)
public class IdleState implements VendingState {
    public void insertCoin(VendingMachine ctx) {
        ctx.setState(new HasMoneyState()); // ❌ IdleState imports HasMoneyState
    }
}

// ✅ FIX — use state enum + factory, states reference only enums
public enum StateType { IDLE, HAS_MONEY, DISPENSING, OUT_OF_STOCK }

public void insertCoin(VendingMachine ctx) {
    ctx.transitionTo(StateType.HAS_MONEY); // ✅ context resolves the class
}

// ❌ BREAKING — invalid state action silently ignored
public class IdleState implements VendingState {
    public void dispense(VendingMachine ctx) { } // ❌ silent no-op
}

// ✅ FIX — fail loudly with meaningful message
public void dispense(VendingMachine ctx) {
    throw new IllegalStateException(
        "Cannot dispense in IDLE state — insert coin first");
}

How Other Patterns Relate

Interview Cheat Sheet

  1. What: Context delegates all behaviour to the current State object. Each state encapsulates its own behaviour and triggers transitions. Client always calls the same Context method — different state means different result.
  2. How: State interface with one method per behaviour. ConcreteState implements all methods for its state. Context holds state: State field and delegates. States call context.setState(next) to transition.
  3. Key distinction from Strategy: Strategy = client picks the algorithm. State = object transitions automatically based on internal events. Strategy is chosen externally; State changes internally.