State Design Pattern


Intent

The State pattern allows an object to change its behavior when its internal state changes — making the object appear, from the outside, to have changed its class. (See p. 283 of the GoF book (Gamma et al. 1995) for the original formulation.)

The pattern is also known as Objects for States. The original motivating example in GoF is a TCPConnection that switches behavior between TCPEstablished, TCPListen, and TCPClosed states — the same Open() request behaves entirely differently depending on which state the connection is currently in.

Problem

The core problem the State pattern addresses is when an object’s behavior needs to change dramatically based on its internal state, and this leads to code that is complex, difficult to maintain, and hard to extend.

If you try to manage state changes using traditional methods, the class containing the state often becomes polluted with large, complex if/else or switch statements that check the current state and execute the appropriate behavior. This results in cluttered code and a violation of the Separation of Concerns design principle, since the code for different states is mixed together and it is hard to see what the behavior of the class is in different states. This also violates the Open/Closed principle, since adding additional states is very hard and requires changes in many different places in the code.

Context

An object’s behavior depends on its state, and it must change that behavior at runtime. You either have many states already or you might need to add more states later.

Solution

Create an abstract State type — either an interface or an abstract class — that defines the operations that all states have. The Context class should not know any state methods besides the methods in the abstract State so that it is not tempted to implement any state-dependent behavior itself. For each state-dependent method (i.e., for each method that should be implemented differently depending on which state the Context is in) we should define one abstract method in the State type.

Create Concrete State classes that implement (or inherit from) the State type and provide the state-specific behavior.

The primary interactions should be between the Context and its current State object. Whether Concrete State objects interact with each other depends on the transition design decision discussed below.

UML Role Diagram

UML Example Diagram

Sequence Diagram

Code Example

This example removes the conditional state checks from GumballMachine. The context delegates each action to the current state object, and the state object performs the transition.

Teaching example: These snippets are intentionally small. They show one reasonable mapping of the pattern roles, not a drop-in architecture. In production, always tailor the pattern to the concrete context: lifecycle, ownership, error handling, concurrency, dependency injection, language idioms, and team conventions.

The full Gumball Machine example from Head First Design Patterns (Ch. 10) actually has four states — NoQuarterState, HasQuarterState, SoldState, and SoldOutState — plus an inventory counter. We’ve collapsed it to two states here so the pattern’s mechanics are visible without the bookkeeping. In a realistic implementation, turnCrank() would transition to a separate SoldState whose dispense() then transitions to either NoQuarterState (more gumballs left) or SoldOutState (count hits zero) — making the value of one-class-per-state immediate the moment you add the WinnerState change request that closes the chapter.

interface State {
    void insertQuarter(GumballMachine machine);
    void turnCrank(GumballMachine machine);
}

final class NoQuarterState implements State {
    public void insertQuarter(GumballMachine machine) {
        System.out.println("You inserted a quarter");
        machine.setState(machine.hasQuarterState());
    }

    public void turnCrank(GumballMachine machine) {
        System.out.println("Insert a quarter first");
    }
}

final class HasQuarterState implements State {
    public void insertQuarter(GumballMachine machine) {
        System.out.println("Quarter already inserted");
    }

    public void turnCrank(GumballMachine machine) {
        machine.releaseBall();
        machine.setState(machine.noQuarterState());
    }
}

final class GumballMachine {
    private final State noQuarter = new NoQuarterState();
    private final State hasQuarter = new HasQuarterState();
    private State state = noQuarter;

    void insertQuarter() {
        state.insertQuarter(this);
    }

    void turnCrank() {
        state.turnCrank(this);
    }

    void setState(State state) {
        this.state = state;
    }

    State noQuarterState() { return noQuarter; }
    State hasQuarterState() { return hasQuarter; }

    void releaseBall() {
        System.out.println("A gumball comes rolling out");
    }
}

public class Demo {
    public static void main(String[] args) {
        GumballMachine machine = new GumballMachine();
        machine.insertQuarter();
        machine.turnCrank();
    }
}
#include <iostream>

class GumballMachine;

struct State {
    virtual ~State() = default;
    virtual void insertQuarter(GumballMachine& machine) = 0;
    virtual void turnCrank(GumballMachine& machine) = 0;
};

class NoQuarterState : public State {
public:
    void insertQuarter(GumballMachine& machine) override;
    void turnCrank(GumballMachine&) override {
        std::cout << "Insert a quarter first\n";
    }
};

class HasQuarterState : public State {
public:
    void insertQuarter(GumballMachine&) override {
        std::cout << "Quarter already inserted\n";
    }
    void turnCrank(GumballMachine& machine) override;
};

class GumballMachine {
public:
    GumballMachine() : state_(&noQuarter_) {}

    void insertQuarter() { state_->insertQuarter(*this); }
    void turnCrank() { state_->turnCrank(*this); }
    void setState(State& state) { state_ = &state; }
    State& noQuarterState() { return noQuarter_; }
    State& hasQuarterState() { return hasQuarter_; }

    void releaseBall() const {
        std::cout << "A gumball comes rolling out\n";
    }

private:
    NoQuarterState noQuarter_;
    HasQuarterState hasQuarter_;
    State* state_;
};

void NoQuarterState::insertQuarter(GumballMachine& machine) {
    std::cout << "You inserted a quarter\n";
    machine.setState(machine.hasQuarterState());
}

void HasQuarterState::turnCrank(GumballMachine& machine) {
    machine.releaseBall();
    machine.setState(machine.noQuarterState());
}

int main() {
    GumballMachine machine;
    machine.insertQuarter();
    machine.turnCrank();
}
from __future__ import annotations

from abc import ABC, abstractmethod


class State(ABC):
    @abstractmethod
    def insert_quarter(self, machine: GumballMachine) -> None:
        pass

    @abstractmethod
    def turn_crank(self, machine: GumballMachine) -> None:
        pass


class NoQuarterState(State):
    def insert_quarter(self, machine: GumballMachine) -> None:
        print("You inserted a quarter")
        machine.state = machine.has_quarter

    def turn_crank(self, machine: GumballMachine) -> None:
        print("Insert a quarter first")


class HasQuarterState(State):
    def insert_quarter(self, machine: GumballMachine) -> None:
        print("Quarter already inserted")

    def turn_crank(self, machine: GumballMachine) -> None:
        machine.release_ball()
        machine.state = machine.no_quarter


class GumballMachine:
    def __init__(self) -> None:
        self.no_quarter = NoQuarterState()
        self.has_quarter = HasQuarterState()
        self.state = self.no_quarter

    def insert_quarter(self) -> None:
        self.state.insert_quarter(self)

    def turn_crank(self) -> None:
        self.state.turn_crank(self)

    def release_ball(self) -> None:
        print("A gumball comes rolling out")


machine = GumballMachine()
machine.insert_quarter()
machine.turn_crank()
interface State {
  insertQuarter(machine: GumballMachine): void;
  turnCrank(machine: GumballMachine): void;
}

class NoQuarterState implements State {
  insertQuarter(machine: GumballMachine): void {
    console.log("You inserted a quarter");
    machine.setState(machine.hasQuarterState());
  }

  turnCrank(): void {
    console.log("Insert a quarter first");
  }
}

class HasQuarterState implements State {
  insertQuarter(): void {
    console.log("Quarter already inserted");
  }

  turnCrank(machine: GumballMachine): void {
    machine.releaseBall();
    machine.setState(machine.noQuarterState());
  }
}

class GumballMachine {
  private readonly noQuarter = new NoQuarterState();
  private readonly hasQuarter = new HasQuarterState();
  private state: State = this.noQuarter;

  insertQuarter(): void {
    this.state.insertQuarter(this);
  }

  turnCrank(): void {
    this.state.turnCrank(this);
  }

  setState(state: State): void {
    this.state = state;
  }

  noQuarterState(): State {
    return this.noQuarter;
  }

  hasQuarterState(): State {
    return this.hasQuarter;
  }

  releaseBall(): void {
    console.log("A gumball comes rolling out");
  }
}

const machine = new GumballMachine();
machine.insertQuarter();
machine.turnCrank();

Design Decisions

How to let the state make operations on the context object?

The state-dependent behavior often needs to make changes to the Context. To implement this, the state object can either store a reference to the Context (usually implemented in the Abstract State class) or the context object is passed into the state with every call to a state-dependent method. The stored-reference approach is simpler when states frequently need context data; the parameter-passing approach keeps state objects more reusable across different contexts.

Who defines state transitions?

This is a critical design decision with significant consequences:

  • Context-driven transitions: The Context class contains all transition logic (e.g., “if state is NoQuarter and quarter inserted, switch to HasQuarter”). This makes all transitions visible in one place but creates a maintenance bottleneck as states grow.
  • State-driven transitions: Each Concrete State knows its successor states and triggers transitions itself (e.g., NoQuarterState.insertQuarter() calls context.setState(new HasQuarterState())). This distributes the logic but makes it harder to see the complete state machine at a glance. It also introduces dependencies between state classes.

In practice, state-driven transitions are preferred when states are well-defined and transitions are local. Context-driven transitions work better when transitions depend on complex external conditions.

State object creation: on demand vs. shared

If state objects are stateless (they carry behavior but no instance data), they can be shared as flyweights or even Singletons, saving memory. GoF (p. 285) lists this as one of the State pattern’s three core consequences: when the state is encoded entirely in the object’s type, contexts can share a single instance per state. If state objects carry per-context data, they must be created on demand instead.

A related trade-off — also from GoF — is when to create state objects: create them only on demand (and destroy them when no longer current) versus create them all up front and keep references forever. On-demand creation is preferable when not all states will be entered and contexts change state infrequently. Up-front creation is better when state changes occur rapidly, so that instantiation costs are paid once and there are no destruction costs.

State pattern vs. table-based state machines

The State pattern is not the only way to structure a state machine in OO code. A long-standing alternative — discussed in GoF (p. 286, citing Cargill’s C++ Programming Style) — is a table-driven machine: a 2D table maps (currentState, input) → nextState, and a single dispatch loop reads from the table.

The trade-off:

  • State pattern models state-specific behavior. Each state is a class; transitions are easy to augment with arbitrary code (logging, side effects, validation).
  • Table-driven models transitions uniformly. The state machine is data, so changing the topology means editing a table, not code — but attaching custom behavior to each transition is awkward, and table look-ups are typically slower than virtual calls.

Use the table-driven approach when the state graph is large, regular, and behavior-poor (e.g., a parser’s lexer states). Use the State pattern when each state needs distinct, non-trivial behavior.

How to represent a state in which the object is never doing anything (either at initialization time or as a “final” state)

Use the Null Object pattern to create a “null state”. This communicates the design intent of “empty behavior” explicitly rather than scattering null checks throughout the code.

The Core Insight: Polymorphism over Conditions

The State pattern embodies the fundamental principle of polymorphism over conditions. Instead of writing:

if (state == "noQuarter") { /* behavior A */ }
else if (state == "hasQuarter") { /* behavior B */ }
// ...one branch per state, repeated in every state-dependent method

…the pattern replaces each branch with a polymorphic object. This is powerful because:

  • Adding a new state requires adding a new class, not modifying existing conditional logic (Open/Closed Principle).
  • The behavior of each state is cohesive and self-contained, rather than scattered across one giant method.
  • The compiler can enforce that every state implements every required method, catching missing cases that a conditional chain silently ignores.

A pedagogically effective way to internalize this insight is the “Before and After” technique: start with the conditional version of a problem, refactor it to use the State pattern, and then try to add a new state to both versions. The difference in effort makes the pattern’s value clear.

State vs. Strategy: Same Structure, Different Intent

The State and Strategy patterns have nearly identical UML class diagrams—a context delegating to an abstract interface with multiple concrete implementations. The difference is entirely in intent:

  • State: The context object’s behavior changes implicitly as its internal state transitions. The client typically does not choose which state object is active. Concrete States often need to know about one another so they can install the next state on the Context.
  • Strategy: The client explicitly selects which algorithm to use. There are no automatic transitions between strategies, and Concrete Strategies are independent of one another.

A useful heuristic: if the concrete implementations transition between each other based on internal logic, it is State. If the client selects the concrete implementation at configuration time, it is Strategy.

Practice

State Pattern Flashcards

Key concepts, design decisions, and trade-offs of the State design pattern.

Difficulty: Basic

What problem does the State pattern solve?

Difficulty: Intermediate

What principle does the State pattern embody?

Difficulty: Advanced

How does State differ from Strategy?

Difficulty: Intermediate

What is a ‘Null State’?

Difficulty: Advanced

Who should define state transitions?

State Pattern Quiz

Test your understanding of the State pattern's design decisions, its relationship to Strategy, and the principle of polymorphism over conditions.

Difficulty: Intermediate

A GumballMachine has states: NoQuarter, HasQuarter, Sold, and SoldOut. Each state’s insertQuarter() method calls context.setState(new HasQuarterState()) to trigger transitions. What design decision is this an example of?

Correct Answer:
Difficulty: Intermediate

The Game of Life represents cells as boolean[][] cells where true means alive and false means dead. Methods contain code like if (cells[i][j] == true) { ... }. Which principle does this violate, and which pattern addresses it?

Correct Answer:
Difficulty: Advanced

The State and Strategy patterns have identical UML class diagrams. What is the key behavioral difference between them?

Correct Answer:
Difficulty: Advanced

A Document class has states: Draft, Review, Published, Archived. A new requirement adds a “Rejected” state that can transition back to Draft. Which transition approach handles this addition more gracefully?

Correct Answer:
Difficulty: Advanced

State objects in a GumballMachine carry no instance data — they only contain behavior methods. A developer proposes making all state objects Singletons to save memory. What is the key risk of this approach?

Correct Answer: