CMD Guide
HomeOO & Low-Level DesignBehavioral

State Pattern

The State pattern replaces a context's if/switch on a status field with a polymorphic object reference: the context holds a pointer to one state object and forwards every request to it, so behavior changes the instant that pointer is reswung to a different state class — and crucially, in the canonical form, each state decides its own successor and reswings the pointer itself, so the transition table lives distributed across the state classes rather than in any central conditional.

That last clause is the whole pattern. Take a document workflow with three states — Draft, Moderation, Published — and two actions, publish() and approve(). The naive version is a status enum and a wall of branches:

void publish() {
  if (status == DRAFT)        status = MODERATION;
  else if (status == MODERATION) /* refuse */ ;
  else if (status == PUBLISHED)  /* refuse */ ;
}
void approve() {
  if (status == DRAFT)        /* refuse */ ;
  else if (status == MODERATION) status = PUBLISHED;
  else if (status == PUBLISHED)  /* refuse */ ;
}

Every new action adds another method full of branches; every new state forces you to edit every action. The State pattern turns each row of that implicit table into a class, and each cell into a method on that class.

diagram
diagram

The code, and why the transition lives inside the state

Notice in the implementation below that Document contains no conditional logic and never decides a transition. It exposes setState and blindly delegates. The Draft object is the thing that knows "after publish I become Moderation," and it performs that change by calling doc.setState(new Moderation()) on the context. This is the canonical, more powerful variant of State — the states form a self-wiring graph.

interface State {
  void publish(Document doc);
  void approve(Document doc);
}

class Document {           // Context: holds the pointer, delegates, never branches
  private State state = new Draft();
  public void setState(State s) { this.state = s; }
  public State currentState()   { return state; }
  public void publish() { state.publish(this); }
  public void approve() { state.approve(this); }
}

class Draft implements State {
  public void publish(Document doc) {
    System.out.println("Draft -> Moderation");
    doc.setState(new Moderation());   // the STATE owns the transition
  }
  public void approve(Document doc) {
    System.out.println("Draft cannot be approved directly."); // refuse, stay
  }
}

class Moderation implements State {
  public void publish(Document doc) {
    System.out.println("Cannot publish from Moderation without approval.");
  }
  public void approve(Document doc) {
    System.out.println("Moderation -> Published");
    doc.setState(new Published());
  }
}

class Published implements State {
  public void publish(Document doc) { System.out.println("Already published."); }
  public void approve(Document doc) { System.out.println("Already approved."); }
}

public class Solution {
  public static void main(String[] args) {
    Document doc = new Document();
    doc.publish();   // Draft -> Moderation
    doc.approve();   // Moderation -> Published
    doc.approve();   // Already approved.
  }
}

Why the naive version is worse, not just longer: the enum-and-switch version centralizes the transition table, so adding a reject() action or an Archived state means editing every existing method and risking a missed branch — the compiler can't tell you you forgot the PUBLISHED case. With State, an unhandled action is a method you simply didn't override, and adding a state is a new class that doesn't touch the others (open/closed).

Traced run with real values

Calling the main above, tracking the context's state pointer (initially Draft):

StepCallPointer dispatches toWhat that state doesPointer afterConsole
0new Document()field initializer runsDraft
1doc.publish()Draft.publishsetState(new Moderation())ModerationDraft -> Moderation
2doc.approve()Moderation.approvesetState(new Published())PublishedModeration -> Published
3doc.approve()Published.approverefuses, no setStatePublishedAlready approved.

The key observation: Document.approve() is the same source code at steps 2 and 3, yet produces a transition then a refusal — because the pointer it forwards to changed underneath it. That is "behavior changes when internal state changes," mechanically.

Pitfalls

When to use it — and State vs Strategy

Reach for State when these signals stack up: an object has a small, named set of modes; the same method behaves differently per mode; and the legal transitions between modes are themselves a rule you care about (a finite-state machine). Order lifecycles, document/content workflows, TCP connections, game scenes, media players, and parsers all fit.

State's structural twin is Strategy — identical class diagrams (a context holding an interface reference, concrete implementations). They are distinguished by one thing, and it is exactly the mechanism this page opened with:

StateStrategy
Who chooses the next implementation?The states themselves — a state calls context.setState(...). They know about each other and form a graph.The client — injected once, the strategy never swaps itself. Strategies don't know each other.
ModelsA lifecycle / transitions over time.Interchangeable algorithms for one fixed task.
Number of swapsMany, driven by events.Usually one, at construction.
Intent"What can I do now, and what do I become next?""How should I do this one thing?"

So the canonical-form code above — where Draft.publish reaches back and reswings the context — is precisely what makes it State and not Strategy. If you deleted those setState calls and let the client pick each state, you'd have Strategy.

Alternatives and their costs. Versus a plain enum + switch: you trade a handful of conditionals for N+1 classes (more files, more indirection, an allocation/lookup per dispatch). For 2–3 states and one action, the switch is genuinely clearer — don't pattern-tax it. Versus a transition table (a Map<(State, Event), State>): the table centralizes the whole graph so you can validate it, visualize it, or load it from config — great for large or data-driven machines — but it can't carry rich per-state behavior the way polymorphic classes can. Versus a dedicated workflow/state-machine library (Spring StateMachine, XState): you get persistence, guards, and history for free, at the cost of a heavy dependency.

Choose State when behavior varies by mode and the modes transition into each other on events; prefer Strategy when the client picks one fixed behavior that never swaps itself; prefer a switch for 2–3 trivial states; prefer a transition table or library when the graph is large, data-driven, or needs persistence and audit.

Scenario: the document workflow needs role-based guards ("only an editor can approve") and must survive restarts. State alone gets you clean per-status behavior, but persistence and guards push you toward a transition table backed by the DB or a state-machine library — keep the State classes for behavior, externalize the graph for durability.

Takeaways


Re-authored and deepened for this guide. Mechanism, transition ownership, and the State-vs-Strategy distinction follow Gamma, Helm, Johnson & Vlissides, Design Patterns (1994); the worked document workflow and singleton-state pitfall draw on Refactoring.Guru's State entry; the transition-table and library trade-offs reflect common practice (Spring StateMachine, XState) and Robert C. Martin's open/closed framing. Code verified to compile and run as shown.

🎯 STANDOUT elevation: Client-chosen vs self-driven (one line staff)

State: current state object calls context.setState(next) — transitions are self-driven. Strategy: client injects algorithm; strategies never swap themselves — client-chosen.

  1. L1: Point to the setState call that makes Draft→Moderation State not Strategy.
  2. L2: Concurrent publish/approve race on state pointer — fix?
  3. L3: When prefer transition table over polymorphic State classes.
🔨 Practice this hands-on — Design an Elevator System →🔨 Practice this hands-on — Design a Vending Machine →
Attempt it from an empty file, break it to feel the failure, then defend it under pushback.
🤖 Don't fully get this? Learn it with Claude

Stuck on State Pattern? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **State Pattern** (OO & Low-Level Design) and want to truly understand it. Explain State Pattern from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **State Pattern** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **State Pattern** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **State Pattern** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes