Design Patterns: Behavioral · Behavioral Patterns

State Pattern

Letting an object change its own behavior as its internal state changes, without branching on that state everywhere.

  • 5 min read
  • 10 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The pattern
  3. How it works, step by step
  4. Where you'll see it
  5. Choosing among the behavioral patterns
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

State is the behavioral pattern for objects whose behavior depends on what stage they're in - an order that can be paid but not shipped yet, a connection that can send only while open. Instead of checking a status field in every method, each state becomes its own class that knows how to handle every action and which state comes next. This skill covers the pattern, how to build a state machine with it, and the alternatives for small machines.

An order moves through statuses: NEW, PAID, SHIPPED, DELIVERED, CANCELLED. The first version keeps a status field and checks it everywhere:

Java

void ship() {
    if (status == NEW) throw new IllegalStateException("not paid yet");
    else if (status == PAID) { status = SHIPPED; notifyWarehouse(); }
    else if (status == SHIPPED || status == DELIVERED) throw new IllegalStateException("already shipped");
    else if (status == CANCELLED) throw new IllegalStateException("cancelled");
}
// ...and the same kind of switch in pay(), cancel(), deliver(), refund()...

Python

def ship(self) -> None:
    if self.status == Status.NEW:
        raise RuntimeError("not paid yet")
    elif self.status == Status.PAID:
        self.status = Status.SHIPPED
        self._notify_warehouse()
    elif self.status in (Status.SHIPPED, Status.DELIVERED):
        raise RuntimeError("already shipped")
    elif self.status == Status.CANCELLED:
        raise RuntimeError("cancelled")
# ...and the same kind of chain in pay(), cancel(), deliver(), refund()...

The rules for one status are spread across every method, and each new status means editing all of them.

We'll go from simple to more technical:

  1. The pattern: a context that delegates to its current state object.
  2. Building a state machine, step by step.
  3. Where you'll see state machines: orders, workflows, connections, games.
  4. Keeping the machine visible and correct - and State versus Strategy.

The pattern

State gives each status its own class. The object (the context) delegates every action to its current state object, and the state decides what happens - including which state comes next.

Java

interface OrderState {
    OrderState pay(Order o);
    OrderState ship(Order o);
    OrderState cancel(Order o);
}

class PaidState implements OrderState {
    public OrderState pay(Order o)    { throw new IllegalStateException("already paid"); }
    public OrderState ship(Order o)   { o.notifyWarehouse(); return new ShippedState(); }
    public OrderState cancel(Order o) { o.refund(); return new CancelledState(); }
}

class Order {
    private OrderState state = new NewState();
    void pay()    { state = state.pay(this); }
    void ship()   { state = state.ship(this); }
    void cancel() { state = state.cancel(this); }
}

Python

from abc import ABC, abstractmethod


class OrderState(ABC):
    @abstractmethod
    def pay(self, o: Order) -> "OrderState": ...

    @abstractmethod
    def ship(self, o: Order) -> "OrderState": ...

    @abstractmethod
    def cancel(self, o: Order) -> "OrderState": ...


class PaidState(OrderState):
    def pay(self, o: Order) -> OrderState:
        raise RuntimeError("already paid")

    def ship(self, o: Order) -> OrderState:
        o.notify_warehouse()
        return ShippedState()

    def cancel(self, o: Order) -> OrderState:
        o.refund()
        return CancelledState()


class Order:
    def __init__(self):
        self._state: OrderState = NewState()

    def pay(self) -> None:
        self._state = self._state.pay(self)

    def ship(self) -> None:
        self._state = self._state.ship(self)

    def cancel(self) -> None:
        self._state = self._state.cancel(self)

Everything about "being paid" now lives in PaidState.

How it works, step by step

  1. List the states and the events (actions) that can happen.
  2. Create a state interface with one method per event.
  3. Write one class per state; each method either performs the action and returns the next state, or rejects it.
  4. The context holds the current state and delegates each event to it, replacing the state with whatever comes back.
  5. Adding a state means adding one class (and updating the states that lead into it).

Where you'll see it

  • Order and payment lifecycles, document workflows (draft, in review, published), ticket systems.
  • Network connections (connecting, open, closing, closed) and media players (playing, paused, stopped).
  • Game characters (idle, running, jumping) and vending machines.
  • Libraries such as Spring Statemachine formalize the same idea.

Choosing among the behavioral patterns

If you need to...Reach forIt's about
Let many objects react when something happens, without the source knowing themObserverWho gets told about a change.
Swap one algorithm for another at runtimeStrategyHow a task is done - chosen from outside.
Turn a request into an object you can queue, log, undo or redoCommandWhat to do, packaged for later.
Make an object behave differently depending on its current stateState (this lesson)Behavior that changes itself as the object moves between states.
Fix the steps of an algorithm but let subclasses fill in some of themTemplate MethodThe skeleton stays, the details vary.

Common mistakes

MistakeWhat happensDo this instead
A status switch in every methodeach new status edits every methodone class (or table entry) per state
Silently ignoring invalid eventsbugs hide; orders get stuckreject them explicitly
One state per combination of independent aspectsdozens of classes (paid-and-wrapped-and-...)separate machines for independent aspects
Transitions scattered so nobody sees the lifecyclereviews and audits are guessworka transition table or a generated diagram
Saving state objects as-iscan't reload thempersist the state's name (and its data)

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. In the State pattern, what does each state class decide?

Show answer

How to handle every event while the object is in that state - including which state comes next, or that the event is rejected.

2. An order in NEW receives ship(). What should happen?

Show answer

Reject it explicitly (an error or a clear result) and stay in NEW. Silently ignoring it hides bugs; shipping an unpaid order breaks the business rules.

3. When is an enum or a transition table better than one class per state?

Show answer

When states mostly just decide the next state: the whole lifecycle then sits in one readable place. Per-state classes win when each state carries a lot of behavior.

Key takeaways

  • Each state is a class that knows how to handle every event - and what state comes next.
  • The context delegates to its current state instead of switching on a status field.
  • Reject invalid transitions clearly; never ignore them silently.
  • For small machines, an enum or a transition table keeps the whole lifecycle visible.
  • The object changes its own state (State); the client picks the algorithm (Strategy).

Quick revise

The whole skill on one page, for the night before an interview.

Everything from the lesson and the deep dive on one page. Skim it before practice or an interview, and open the full lesson for anything that doesn't ring a bell.

Pick the tool

If you need...Reach for
Rich behavior per stateone class per state
States that mostly pick the next statea transition table (map / dict)
Data that exists only in some statessealed records / dataclass unions
Independent aspects (payment, fulfillment)separate machines
The client choosing the behaviorStrategy, not State

Snippets worth knowing by heart

Java

static final Map<State, Map<Event, State>> TRANSITIONS = Map.of(
    State.NEW,     Map.of(Event.PAY, State.PAID, Event.CANCEL, State.CANCELLED),
    State.PAID,    Map.of(Event.SHIP, State.SHIPPED, Event.CANCEL, State.CANCELLED),
    State.SHIPPED, Map.of(Event.DELIVER, State.DELIVERED));

State next(State s, Event e) {
    State n = TRANSITIONS.getOrDefault(s, Map.of()).get(e);
    if (n == null) throw new IllegalStateException(e + " not allowed in " + s);
    return n;
}

Python

TRANSITIONS = {
    ("NEW", "pay"): "PAID", ("NEW", "cancel"): "CANCELLED",
    ("PAID", "ship"): "SHIPPED", ("PAID", "cancel"): "CANCELLED",
    ("SHIPPED", "deliver"): "DELIVERED",
}

def next_state(state: str, event: str) -> str:
    try:
        return TRANSITIONS[(state, event)]
    except KeyError:
        raise RuntimeError(f"{event} not allowed in {state}") from None

The traps

  • Status switches in every method.
  • Silently ignored invalid events.
  • Combined states for independent aspects.
  • A lifecycle nobody can see in one place.

Staff-level points, one line each

  • Keep one view of the machine: a transition table or a state diagram; test all state-event pairs (20 here, 5 allowed).
  • States carrying their own data (sealed records, dataclass unions) make illegal states unrepresentable.
  • Concurrent transitions: blind writes let a cancel overwrite a shipment; a conditional UPDATE ... WHERE status = 'PAID' updated 1 row, then 0.
  • Events arrive twice and out of order: idempotent transitions, explicit early-event policy, sequence numbers.
  • Timeouts are events from a durable scheduler, applied with the same conditional transition.
  • State explosion: orthogonal regions (separate machines) and hierarchical states (Harel's statecharts).

Say it in an interview

  • "Each order state knows which actions it allows and what comes next."
  • "The transition table is the whole lifecycle; a test checks every pair."
  • "Shipping updates the row only WHERE status = 'PAID', so a concurrent cancel can't win."
  • "Payment and fulfillment are two small machines, not one big one."

Practice

This skill comes with 10 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice State Pattern free

A Vending Machine Built From State Objects

MediumProCoding problemJava · Python

The machine sells one product at price cents. It delegates every action to its current state object, and each state decides what happens - including which state comes next (m.setState(...)). Implement the three states:

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

An Order Lifecycle as an Enum State Machine

MediumProCoding problemJava · Python

Small state machines read beautifully as an enum: each constant overrides on(event, order) and returns the next state - or null when the event isn't allowed there.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice State Pattern free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime