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/switchis 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
Stateobject, exposessetState(s: State)for transition. All state-sensitive method calls delegate tothis.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 — cautionJava 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.
Related Concepts
| Pattern | Relationship |
|---|---|
| Strategy-Pattern | Structural twin — same object structure, different intent; see the callout above for the definitive comparison |
| Command-Pattern | Command can trigger state transitions; the commands are the transitions in event-sourced state machines |
| Flyweight | State 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
Backlinks
Notes that link here: GoF-Patterns-MOC