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.
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):
| Step | Call | Pointer dispatches to | What that state does | Pointer after | Console |
|---|---|---|---|---|---|
| 0 | new Document() | — | field initializer runs | Draft | — |
| 1 | doc.publish() | Draft.publish | setState(new Moderation()) | Moderation | Draft -> Moderation |
| 2 | doc.approve() | Moderation.approve | setState(new Published()) | Published | Moderation -> Published |
| 3 | doc.approve() | Published.approve | refuses, no setState | Published | Already 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
- Allocating a fresh state on every transition.
new Moderation()per call is wasteful and, worse, breaks equality/identity checks. If states are stateless (the usual case), make them singletons (anenumin Java, a package-level value in Go) and reswing the pointer to the shared instance. - Putting transition logic back in the context. A common regression is the context peeking at the current state's type (
if (state instanceof Draft) ...) to decide something. That smuggles the switch back in and defeats the pattern — the whole point is the context does not know the concrete state. - State objects that hoard context data. If a concrete state needs the document's author, title, etc., pass the context in (as we do with
doc) rather than copying fields into the state. Duplicated fields drift out of sync the moment the pointer swings. - Lost transitions / silent self-loops. Methods that "refuse" by doing nothing make illegal transitions invisible. In real workflows, throw an
IllegalStateTransitionExceptionor return a result so callers and audits can see the rejection — silence hides bugs. - Concurrency. The context's
statefield is shared mutable state. If two threads callpublish()andapprove()on the same document, the pointer swing is a read-modify-write race. Guard the context (lock, or an atomic compare-and-set on the state reference) the same way you would any mutable field. - Transition side-effects fire on every entry. If entering
Publishedsends an email, make sure that lives in one place (anonEnterhook), or you'll duplicate or skip it as the graph grows.
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:
| State | Strategy | |
|---|---|---|
| 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. |
| Models | A lifecycle / transitions over time. | Interchangeable algorithms for one fixed task. |
| Number of swaps | Many, 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
- State = one polymorphic pointer the context blindly delegates to; swinging the pointer is the behavior change. No conditionals in the context.
- In the canonical form, each state owns its outgoing transition by calling
context.setState(...). This self-wiring is the single trait that distinguishes State from the structurally identical Strategy. - It buys open/closed extensibility (new state = new class) at the cost of more classes, indirection, and per-dispatch overhead — not worth it for 2–3 trivial cases.
- For large, data-driven, or persisted machines, externalize the graph (transition table / library) and keep State classes only for the per-state behavior.
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.
- L1: Point to the
setStatecall that makes Draft→Moderation State not Strategy. - L2: Concurrent publish/approve race on state pointer — fix?
- L3: When prefer transition table over polymorphic State classes.
🤖 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.
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.
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.
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.
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.