State Pattern
Let an object change its behaviour when its internal state changes — the object appears to change its class. The foundation of finite state machines, order lifecycles, UI modes, and protocol handlers.
Why State Exists
You are building a vending machine. It has states: Idle (waiting for coins), HasMoney (coins inserted, waiting for selection), Dispensing (delivering product), and OutOfStock. Each action (insert coin, select product, dispense, refund) behaves differently depending on the current state. The naive approach: every method contains a giant switch on the current state.
What goes wrong:
- Scattered state logic — logic for “HasMoney” state is spread across
insertCoin(),selectProduct(),dispense(), andrefund(); you can’t see all rules for one state in one place - Open/Closed violation — adding a new state (e.g. “Maintenance”) means editing every method on the class
- Combinatorial explosion — with 5 states and 4 methods, you have 20 switch cases. At 10 states ร 6 methods = 60 cases in one class
- Transition bugs — forgetting a case in one switch leaves the machine in an inconsistent state; the compiler can’t enforce completeness across scattered switches
- Untestable — you can’t test “HasMoney” behaviour in isolation; you must construct the entire machine
This is the problem State solves — extract the behaviour for each state into its own class. The vending machine delegates actions to the current state object. Changing state = swapping the delegate. Each state class encapsulates all logic for that state in one place.
What Is State?
State is a behavioral pattern that lets an object alter its behaviour when its internal state changes. The object appears to change its class. Instead of using conditionals to check state in every method, you encapsulate state-specific behaviour in separate state classes. The context (vending machine) holds a reference to the current state object and delegates all behaviour to it. State transitions = swapping the current state object for a new one.
— Gamma, Helm, Johnson, Vlissides (1994)
Key insight: State pattern models a finite state machine (FSM) using objects instead of conditionals. Each state is a class; each transition is a method call that swaps the context’s state reference. This is different from Strategy: Strategy swaps behaviour from the outside (client decides); State transitions from the inside (the current state decides the next state). The object appears to “change its class” because its entire behaviour set changes when the state changes.
State interface with all context methodsHasMoneyState.java.currentState.action()insertCoin() โ state.insertCoin(this).context.setState(new NextState())MaintenanceState — existing states and the context are unchanged.Participants & Structure
| Participant | Role | In the Analogy |
|---|---|---|
| Context | The object whose behaviour changes. Holds a reference to the current State object. Delegates all state-dependent operations to it. Exposes a setState() method for state transitions. | The traffic light unit — same physical box, different behaviour depending on which color is active. |
| State (interface) | Declares the same methods that the context exposes (e.g. insertCoin(), selectProduct()). Every concrete state implements all of them. | The “color behavior contract” — every color must define what happens when a car arrives, when a timer ticks, etc. |
| Concrete State | Implements state-specific behaviour for all context methods. Triggers transitions by calling context.setState(nextState). Each concrete state knows which state follows it. | Green (cars go, timer โ Yellow), Yellow (cars slow, timer โ Red), Red (cars stop, timer โ Green). |
- Both have a Context holding an interface reference with interchangeable implementations.
- The difference: Strategy is swapped from the outside (client decides which algorithm).
- State transitions from the inside (the current state object decides the next state).
- In State, the object “seems to change its class” because it autonomously transitions.
- In Strategy, the client explicitly injects a new strategy.
The Pattern In Motion
Scenario: A vending machine. The user inserts a coin, selects a product, and the machine dispenses it. Each action triggers state transitions.
IdleState. User calls machine.insertCoin()state.insertCoin(this). IdleState accepts the coin and calls ctx.setState(new HasMoneyState()). Machine is now in HasMoney.machine.selectProduct()HasMoneyState.selectProduct(). It validates the selection, then calls ctx.setState(new DispensingState(product)). Machine is now Dispensing.DispensingState automatically delivers the productDispensingState.dispense() delivers the item, decrements stock, and calls ctx.setState(stock > 0 ? new IdleState() : new OutOfStockState()).machine.selectProduct() while in IdleStateIdleState.selectProduct() prints “Insert coin first.” No transition. The state rejects invalid actions gracefully.- The UML (S03) shows who participates. The FSM diagram above shows all possible transitions.
- For State, the key runtime insight is: the context never decides what to do — the current state object handles every action and triggers its own transition.
- The context just calls
state.action(this).
You Already Use This
State is less visible in the JDK than patterns like Iterator or Observer, but it appears in protocol handlers, thread lifecycles, and I/O stream management — anywhere behaviour changes based on internal state.
java.lang.Thread — a thread has states: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED. Calling start() on a TERMINATED thread throws IllegalThreadStateException. Calling wait() moves to WAITING. The thread’s behaviour changes based on its Thread.State — a textbook state machine (though implemented with enums + conditionals rather than the GoF pattern). javax.faces.lifecycle.Lifecycle — JSF request processing has 6 phases (Restore View โ Apply Requests โ Process Validations โ Update Model โ Invoke Application โ Render Response). Each phase is a state that processes the request differently. Phase transitions are managed by the lifecycle. java.net.Socket — a socket has states: created, bound, connected, closed. Calling connect() on an already-connected socket throws an exception. Calling getInputStream() on a closed socket throws. The socket’s API behaviour depends on its connection state. Spring StateMachine — while not JDK stdlib, Spring’s spring-statemachine module is a full implementation of the State pattern. You define states, transitions, actions, and guards declaratively. Used for order workflows, approval processes, and protocol handling. - The JDK mostly uses
enum + switchfor state machines (likeThread.State). - The GoF State pattern uses objects instead. When to prefer objects over enums?
- When each state has significant behaviour (many methods), when states have their own internal data, or when you need Open/Closed (add states without editing existing code).
- For simple state machines (3-4 states, minimal logic),
enum + switchis perfectly fine.
Build It Once
Domain: Vending Machine. A VendingMachine context delegates to a VendingState interface. Four concrete states (IdleState, HasMoneyState, DispensingState, OutOfStockState) each implement all actions and manage their own transitions.
- In this implementation, state objects trigger transitions by calling
ctx.setState(). - This is the GoF approach — each state knows its successor.
- An alternative: the context manages transitions (state objects just return the next state).
- GoF approach is more decoupled; context approach gives centralized control.
- Choose based on how complex your transition logic is.
Common Mistakes
if (state instanceof HasMoney) setState(new Dispensing()), you’ve recreated the original switch statement inside the context. The whole point is that states manage their own transitions. Fix: Each state class calls ctx.setState(nextState) from inside its own methods. The context should have zero conditionals about its state.
- If you write
ctx.setState(new IdleState())every time, you create a new object on each transition. - For stateless states (like
IdleState), this wastes memory on hot paths. - Fix: Use singletons or pre-allocated constants for stateless states:
ctx.setState(IdleState.INSTANCE). Only create new objects for states that carry data (e.g.DispensingState(product)).
- If
VendingStatehas 10 methods but most states only care about 2-3, every state class is forced to write 7-8 no-op implementations. - Fix: Provide a
BaseStateabstract class with default no-op (or exception-throwing) implementations. Concrete states override only the methods they care about. Or use sealed interfaces + records in Java 17+.
- If the “state” is set by the client externally and never transitions autonomously, it’s Strategy, not State.
- State transitions happen from inside the state objects.
- If external code calls
setAlgorithm(), that’s Strategy. - Rule: State = self-transitioning FSM. Strategy = externally-injected algorithm.
- If
IdleStateimportsHasMoneyStatewhich importsDispensingStatewhich importsIdleState— a circular dependency. - Fix: All states depend only on the
Stateinterface and theContext. Create next states lazily or use a state factory/registry.
When To Use State
- An object’s behaviour depends entirely on its current state and changes at runtime — every method behaves differently per state
- You have many conditionals that check the same state variable across multiple methods — the switch/if pattern is scattered
- State transitions are well-defined and finite — you can draw a state machine diagram with clear transitions
- You need states to be independently testable — test “what does HasMoney do on selectProduct?” without full machine setup
- You want to add new states without editing existing states or the context (Open/Closed Principle)
- The object has only 2-3 simple states with minimal behaviour — an enum + switch is simpler; State pattern adds class overhead
- Behaviour changes are externally driven (client injects behaviour) — use Strategy instead
- You only need to restrict method calls per state (e.g. throw exception) — a simple guard clause is enough
- Transitions are not self-contained — if complex external conditions determine the next state, consider a state machine library instead
| Pattern | Who Triggers Change? | Purpose | When to pick it |
|---|---|---|---|
| State ← this | The state object itself (internal) | Object appears to change class as internal state changes | FSM with many states and complex per-state behaviour |
| Strategy | The client (external injection) | Swap interchangeable algorithms at runtime | One behaviour varies; no internal transitions |
| Command | The invoker (executes encapsulated request) | Encapsulate a request for undo/queue/log | Actions as objects, not behaviour that changes with state |
| Template Method | The base class (fixed skeleton) | Vary steps of an algorithm via inheritance | Fixed algorithm with customizable steps, no runtime state changes |
Problems To Solve
State pattern problems test whether you can model a finite state machine as objects, implement clean transitions, handle invalid actions per state, and avoid the scattered-switch anti-pattern.
| Difficulty | Problem | Key Insight |
|---|---|---|
| Easy | Document Lifecycle A document has states: Draft, Review, Published, Archived. In Draft, you can edit and submit for review. In Review, you can approve (โ Published) or reject (โ Draft). In Published, you can archive. In Archived, nothing can be done. Implement with State pattern — each state rejects invalid operations. | Tests a simple linear FSM. Each state has 2-3 valid actions and rejects the rest. The key: DraftState.submit() transitions to ReviewState; ReviewState.approve() transitions to PublishedState. Invalid calls print a message without transitioning. |
| Medium | Audio Player Build a music player with states: Stopped, Playing, Paused. Actions: play(), pause(), stop(), next(), prev(). In Stopped: play starts from beginning; pause/next/prev do nothing. In Playing: play is no-op, pause pauses, stop stops, next/prev change track. In Paused: play resumes, stop goes to stopped, next starts next track. Track changes trigger display updates. | Tests a state machine where the same action (e.g. play()) has different effects per state: starts in Stopped, resumes in Paused, no-op in Playing. The context holds track index and position; states read and modify context data. Tests that states can access context’s internal data cleanly. |
| Medium | TCP Connection Model a TCP connection with states: Closed, Listen, Established, CloseWait. Actions: open(), listen(), send(data), receive(), close(). In Closed: open โ Established (client) or listen โ Listen (server). In Listen: receive connection โ Established. In Established: send/receive work, close โ CloseWait. In CloseWait: acknowledge โ Closed. | Tests a real-world protocol state machine. The interesting part: some actions are only valid in specific states (send only works in Established). States must carry connection data (buffer, sequence number). Tests that State pattern can model network protocols โ the original GoF use case for this pattern. |
| Hard | Order Workflow with Guard Conditions E-commerce order states: Created โ Paid โ Shipped โ Delivered โ Completed. Add: Cancelled (from Created/Paid only), Returned (from Delivered within 30 days). Guards: can’t ship without payment confirmation, can’t return after 30 days. Implement with State pattern + guard conditions. Add an event log that records every transition with timestamp. | Tests advanced State with guard conditions (transitions that depend on external data like time or payment status). Guards are checked inside state classes before transitioning. The event log tests observability — wrap setState() to emit events. Tests handling of “conditional transitions” (same action, different outcome based on guards) cleanly within the State pattern. |
- When asked about State, the interviewer wants to see: (1) a
Stateinterface with all context methods; (2) concrete state classes that implement all methods and trigger transitions viactx.setState(); (3) a context with zero state-checking conditionals — it just delegates; (4) clear explanation of how it differs from Strategy (“State transitions from inside; Strategy is injected from outside”). - Stand-out answers mention:
Thread.Stateas a JDK example, when to prefer enum+switch (simple FSM) vs. - State pattern (complex per-state behaviour), and that State pattern is essentially an object-oriented implementation of a finite state machine.