State Pattern

7 min read

Reading Progress0%
Object-Oriented Design Index
Tier 1 -- Foundations
Tier 2
Tier 3
Tier 4
Tier 5
Object-Oriented Design Index
Tier 1 -- Foundations
Tier 2
Tier 3
Tier 4
Tier 5

State Pattern

1. What Is It?

The pattern allows an object to alter its behavior when its internal changes. The object will appear to change its class. Each state is encapsulated in its own class, and the context delegates behavior to the current state object.

Without it, state-dependent behavior ends up as a tangle of if/elif/switch blocks inside a single class — every method that cares about state must check it, every new state requires editing every method. The class grows proportionally to states × transitions × behaviors. The State pattern externalizes each state into its own class, making transitions explicit and behavior self-contained.


QUICK CHECK

A media player class handles playback differently depending on whether it is in a Playing, Paused, or Stopped state. Currently, every method like play(), pause(), and stop() begins with a series of if/elif checks against a current_state variable. A fourth state, Buffering, needs to be added. What is the primary maintenance problem with this approach?

Choose one answer

2. How It Works

Participants:

  • declaring the behavior methods that vary by
  • ConcreteState — implements behavior for a specific state; may also trigger state transitions by calling context.set_state()
  • Context — holds a reference to the current State object; delegates method calls to it; exposes set_state() for transitions

Step-by-step:

  1. Identify the state-dependent behaviors (the methods that have if state == X: ... logic)
  2. Extract a with those methods
  3. Create a ConcreteState class for each state — each implements all State methods with the right behavior for that state
  4. Context delegates to current_state.method() — no conditionals needed
  5. Transitions: when a state's method determines the next state, it calls context.set_state(NextState())

Python:

from __future__ import annotations
from abc import ABC, abstractmethod


class VendingMachineState(ABC):
    @abstractmethod
    def insert_coin(self, context: VendingMachine) -> None:
        pass

    @abstractmethod
    def select_item(self, context: VendingMachine) -> None:
        pass

    @abstractmethod
    def dispense(self, context: VendingMachine) -> None:
        pass


class IdleState(VendingMachineState):
    def insert_coin(self, context: VendingMachine) -> None:
        print("Coin inserted.")
        context.set_state(CoinInsertedState())

    def select_item(self, context: VendingMachine) -> None:
        print("Please insert a coin first.")

    def dispense(self, context: VendingMachine) -> None:
        print("Please insert a coin first.")


class CoinInsertedState(VendingMachineState):
    def insert_coin(self, context: VendingMachine) -> None:
        print("Coin already inserted.")

    def select_item(self, context: VendingMachine) -> None:
        print("Item selected.")
        context.set_state(ItemSelectedState())

    def dispense(self, context: VendingMachine) -> None:
        print("Please select an item first.")


class ItemSelectedState(VendingMachineState):
    def insert_coin(self, context: VendingMachine) -> None:
        print("Please wait — dispensing in progress.")

    def select_item(self, context: VendingMachine) -> None:
        print("Item already selected.")

    def dispense(self, context: VendingMachine) -> None:
        print("Dispensing item. Enjoy!")
        context.set_state(IdleState())


class VendingMachine:
    def __init__(self) -> None:
        self._state: VendingMachineState = IdleState()

    def set_state(self, state: VendingMachineState) -> None:
        self._state = state

    def insert_coin(self) -> None:
        self._state.insert_coin(self)

    def select_item(self) -> None:
        self._state.select_item(self)

    def dispense(self) -> None:
        self._state.dispense(self)


# Usage
machine = VendingMachine()
machine.select_item()   # Please insert a coin first.
machine.insert_coin()   # Coin inserted.
machine.insert_coin()   # Coin already inserted.
machine.select_item()   # Item selected.
machine.dispense()      # Dispensing item. Enjoy!
machine.dispense()      # Please insert a coin first.

Java:

// State interface
public interface VendingMachineState {
    void insertCoin(VendingMachine context);
    void selectItem(VendingMachine context);
    void dispense(VendingMachine context);
}

// Context
public class VendingMachine {
    private VendingMachineState currentState;

    public VendingMachine() {
        this.currentState = new IdleState();
    }

    public void setState(VendingMachineState state) {
        this.currentState = state;
    }

    public void insertCoin() { currentState.insertCoin(this); }
    public void selectItem() { currentState.selectItem(this); }
    public void dispense()   { currentState.dispense(this); }
}

// ConcreteState: Idle
public class IdleState implements VendingMachineState {
    @Override
    public void insertCoin(VendingMachine context) {
        System.out.println("Coin inserted.");
        context.setState(new CoinInsertedState());
    }

    @Override
    public void selectItem(VendingMachine context) {
        System.out.println("Please insert a coin first.");
    }

    @Override
    public void dispense(VendingMachine context) {
        System.out.println("Please insert a coin first.");
    }
}

// ConcreteState: CoinInserted
public class CoinInsertedState implements VendingMachineState {
    @Override
    public void insertCoin(VendingMachine context) {
        System.out.println("Coin already inserted.");
    }

    @Override
    public void selectItem(VendingMachine context) {
        System.out.println("Item selected.");
        context.setState(new ItemSelectedState());
    }

    @Override
    public void dispense(VendingMachine context) {
        System.out.println("Please select an item first.");
    }
}

// ConcreteState: ItemSelected
public class ItemSelectedState implements VendingMachineState {
    @Override
    public void insertCoin(VendingMachine context) {
        System.out.println("Please wait — dispensing in progress.");
    }

    @Override
    public void selectItem(VendingMachine context) {
        System.out.println("Item already selected.");
    }

    @Override
    public void dispense(VendingMachine context) {
        System.out.println("Dispensing item. Enjoy!");
        context.setState(new IdleState());
    }
}

QUICK CHECK

In the State pattern, when a ConcreteState's method determines that the system should move to a new state, how is that transition typically triggered?

Choose one answer

3. Variants & Comparisons

ApproachHow It WorksProsConsBest For
State PatternEach state is a class; transitions via context.setState()OCP-compliant, each state isolated, easy to add statesMore classes, object allocation per transitionComplex state machines with distinct per-state behavior
Enum + SwitchState stored as enum; switch in every methodSimple, all logic in one fileViolates OCP, methods grow with statesSmall, stable state machines (2–3 states)
State Table / MapExplicit {state: {event: (action, nextState)}} tableVery clear transition modelLess flexible for complex actionsFinite automata, protocol parsers

Who triggers transitions:

  • -driven: ConcreteState calls context.set_state() knows its successors
  • Context-driven: Context holds transition logic — states are pure behavior, no knowledge of others

State-driven is more common and more encapsulated; Context-driven is easier to visualize transitions in one place.


QUICK CHECK

A team is building a backend service that manages TCP connection states (e.g., CLOSED, LISTEN, SYN_RECEIVED, ESTABLISHED, FIN_WAIT). The states follow well-defined transition rules, and the primary concern is having a clear, readable model of which events trigger which transitions. Which approach is the best fit for this use case?

Choose one answer

4. When to Use It (and When NOT To)

Use when:

  • An object's behavior varies significantly based on its , and that behavior can be modeled as a finite set of distinct states
  • You have growing if/elif/switch blocks gated on a variable in multiple methods
  • State transitions are complex and you want them explicit

Don't use when:

  • The state machine has only 2–3 states that won't grow — a simple enum + switch is clearer
  • State-specific behavior is trivial (one line) — the overhead of extra classes isn't justified

Decision triggers:

  • "Every method in this class starts with if self.state == X: ..." → State pattern
  • "Adding a new state requires editing 5 methods" → State pattern
  • "I have a document that can be Draft, Under Review, Approved, or Archived" → State pattern

Anti-patterns:

  • State explosion: Too many states → too many classes. Consider whether some states can be combined or whether a state table is clearer.
  • Putting transition logic in Context: Context becomes a god object managing all transitions. Let states manage their own successors where possible.

QUICK CHECK

A team is building an e-commerce order management system. The Order class has methods like process(), cancel(), and ship(), and every one of these methods begins with a block like if self.status == 'pending': ... elif self.status == 'paid': ... elif self.status == 'shipped': .... Adding a new order status (e.g., 'backordered') requires updating all five methods. Which design decision is most appropriate here?

Choose one answer

5. Real-World Usage

1. TCP connection states: A TCP connection moves through CLOSED → SYN_SENT → ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED. Each handles incoming packets differently — receive(SYN) in CLOSED starts a handshake; in ESTABLISHED state it's an error. State pattern maps directly to this model.

2. Order management systems: An Order has states: PENDING → CONFIRMED → SHIPPED → DELIVERED → CANCELLED. Each state defines which operations are valid (ship() is only valid in CONFIRMED; cancel() is invalid after SHIPPED). State pattern prevents invalid transitions without giant if blocks.

3. Java thread states: A Java Thread has states NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED defined by Thread.State enum. The JVM's internal thread scheduler behaves like a State machine — each state determines which operations (start, sleep, join, interrupt) are valid.


QUICK CHECK

An e-commerce platform models orders with the states: PENDING → CONFIRMED → SHIPPED → DELIVERED. A developer needs to implement a cancel() operation. Using the State pattern, what is the most appropriate way to handle calling cancel() on an order that is already in the SHIPPED state?

Choose one answer

6. Interview Cheat Sheet

Key sentences:

  1. " replaces -conditional logic scattered across methods with — each state is a class that implements the full behavior for that state."
  2. "The key benefit is Open/Closed: adding a new state means adding a new class, not editing every existing method."
  3. "There are two transition models: state-driven (ConcreteState calls context.setState()) and context-driven (Context holds the transition table). State-driven is more encapsulated."
  4. "State and look identical structurally — both delegate behavior to an encapsulated object. The difference is intent: State transitions internally based on object lifecycle; is injected externally by the client."

Common follow-up questions:

Q: How is State different from Strategy? A: Structurally nearly identical. The difference is semantic and in who drives changes: State transitions happen based on the object's own logic (lifecycle-driven); Strategy is chosen and injected by the client. A Strategy doesn't know about other Strategies; a State typically knows its successor states.

Q: Who should own the transition logic — the state or the context? A: Either can work. State-driven is more encapsulated and keeps transition logic close to the state that triggers it. Context-driven makes all transitions visible in one place. I'd choose state-driven for complex systems and context-driven when a visual state table is needed for clarity.

Q: How do you test state machines? A: Unit-test each ConcreteState independently by creating it directly and calling its methods with a mock Context. Test transitions by asserting context.setState() was called with the expected next state.

Connections:

  • State vs. Strategy → same structure, different intent and transition ownership
  • State + → Commands can trigger state transitions
  • State is the object-oriented formalization of a Finite State Machine (FSM)
Glossary History

Click dotted jargon to save explanations here.

Glossary History

Click dotted jargon to save explanations here.