State Pattern

State Pattern

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

Intent

The State pattern externalises each state-specific behaviour into a separate class. The Context holds a reference to the current State object and delegates all state-sensitive requests to it. When a transition is needed, the current State object itself calls context.setState(new NextState()) — this self-transition authority is the defining characteristic of State (versus Strategy). The result is that adding a new state means adding a new class, not modifying existing conditional logic.

When NOT to Use

  • States are few (2–3) and simple — a boolean or enum with an if/switch is clearer and has less indirection overhead
  • Transitions are externally controlled by the client (use Strategy-Pattern instead — the client holds the "algorithm" reference and swaps it)
  • The state machine logic is trivial and will not grow — the class proliferation cost exceeds the benefit

When to Use

  • Object behaviour depends on its state and must change at runtime
  • State transitions are complex and need to be localised in one place, rather than scattered across conditionals
  • You want to avoid large conditional blocks that switch on object state throughout the class

How It Works

Participants:

  • Context — holds a reference to the current State object, exposes setState(s: State) for transition. All state-sensitive method calls delegate to this.currentState.
  • State — interface (or abstract class) declaring the behaviour method(s) that differ per state (e.g., handle(context)).
  • ConcreteState — implements the behaviour for exactly one state. Crucially, it calls context.setState(new NextState()) internally when a transition should occur. The Context does not control transitions; the current State does.

State Diagram

stateDiagram-v2
    [*] --> IdleState
    IdleState --> ActiveState : start()
    ActiveState --> PausedState : pause()
    PausedState --> ActiveState : resume()
    ActiveState --> CompletedState : finish()
    PausedState --> IdleState : reset()
    CompletedState --> IdleState : reset()
    CompletedState --> [*]

    note right of IdleState : Initial state
    note right of ActiveState : Processing
    note right of PausedState : Suspended
    note right of CompletedState : Terminal state

TypeScript Example

// State Pattern — TypeScript
// Source: GoF Design Patterns, p.305
 
interface TrafficLightState {
  handle(ctx: TrafficLight): void;
}
 
class RedState implements TrafficLightState {
  handle(ctx: TrafficLight) {
    console.log("Red — stop");
    ctx.setState(new GreenState());   // self-transition authority
  }
}
 
class GreenState implements TrafficLightState {
  handle(ctx: TrafficLight) {
    console.log("Green — go");
    ctx.setState(new YellowState());  // self-transition authority
  }
}
 
class YellowState implements TrafficLightState {
  handle(ctx: TrafficLight) {
    console.log("Yellow — caution");
    ctx.setState(new RedState());     // self-transition authority
  }
}
 
class TrafficLight {  // Context
  constructor(private state: TrafficLightState = new RedState()) {}
  setState(s: TrafficLightState) { this.state = s; }
  request() { this.state.handle(this); }
}
 
const light = new TrafficLight();
light.request(); // Red — stop
light.request(); // Green — go
light.request(); // Yellow — caution

Java Example

// State Pattern — Java
// Source: GoF Design Patterns, p.305
 
interface TrafficLightState {
    void handle(TrafficLight ctx);
}
 
class RedState implements TrafficLightState {
    public void handle(TrafficLight ctx) {
        System.out.println("Red — stop");
        ctx.setState(new GreenState());   // self-transition authority
    }
}
 
class GreenState implements TrafficLightState {
    public void handle(TrafficLight ctx) {
        System.out.println("Green — go");
        ctx.setState(new YellowState());  // self-transition authority
    }
}
 
class YellowState implements TrafficLightState {
    public void handle(TrafficLight ctx) {
        System.out.println("Yellow — caution");
        ctx.setState(new RedState());     // self-transition authority
    }
}
 
class TrafficLight {  // Context
    private TrafficLightState state = new RedState();
    public void setState(TrafficLightState s) { this.state = s; }
    public void request() { state.handle(this); }
}

State vs Strategy — The Structural Twins Both patterns use the same object structure: Context holds a reference to an interface, delegates calls to it. The difference is behavioural and intentional:

  • State: The State object itself calls context.setState(...) to trigger transitions. The Context does not control the flow — the current State does. Use when an object's behaviour depends on its own lifecycle.
  • Strategy: The strategy is swapped externally by the client or context. No strategy ever changes the context's strategy by itself. Use when you want to choose an algorithm at runtime without the algorithm knowing anything about its container.

Warning signs: A "State" object that never calls context.setState() internally is probably a Strategy. A "Strategy" object that autonomously changes the context's algorithm is probably a State.

PatternRelationship
Strategy-PatternStructural twin — same object structure, different intent; see the callout above for the definitive comparison
Command-PatternCommand can trigger state transitions; the commands are the transitions in event-sourced state machines
FlyweightState objects can be shared as Flyweights when they carry no instance-specific state (e.g., a single RedState instance shared by all lights)

Sources

  • Gamma, Helm, Johnson, Vlissides — Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1994, p.305

Notes that link here: GoF-Patterns-MOC