Behavioral

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.

Overview ยท Behavioral ยท Singleton ยท Factory Method ยท Abstract Factory ยท Builder ยท Prototype ยท Adapter ยท Bridge ยท Composite ยท Decorator ยท Facade ยท Flyweight ยท Proxy ยท Observer ยท Strategy ยท Command ยท Template Method ยท Chain of Resp. ยท State ยท Mediator ยท Iterator ยท Visitor ยท Memento ยท Interpreter
Category: Behavioral Difficulty: Intermediate Interview: Tier 2 Confused with: Strategy
01
Section One ยท The Problem

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.

Naive approach — switch on state in every method
// โœ— Every method has a switch โ€” state logic scattered everywhere class VendingMachine { enum State { IDLE, HAS_MONEY, DISPENSING, OUT_OF_STOCK } private State state = State.IDLE; public void insertCoin() { switch (state) { case IDLE -> state = State.HAS_MONEY; case HAS_MONEY -> System.out.println("Already has money"); case DISPENSING -> System.out.println("Wait, dispensing..."); case OUT_OF_STOCK -> System.out.println("Refunding โ€” out of stock"); } } public void selectProduct() { switch (state) { // โ† same pattern, different actions case IDLE -> System.out.println("Insert coin first"); case HAS_MONEY -> state = State.DISPENSING; case DISPENSING -> System.out.println("Already dispensing"); case OUT_OF_STOCK -> System.out.println("Out of stock"); } } // dispense(), refund() โ€” same switches. Add a 5th state? Edit ALL methods. }

What goes wrong:

  • Scattered state logic — logic for “HasMoney” state is spread across insertCoin(), selectProduct(), dispense(), and refund(); 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
Without State — every method switches on current state
VendingMachine insertCoin() โ†’ switch(state) {...} selectProduct() โ†’ switch(state) {...} dispense() โ†’ switch(state) {...} refund() โ†’ switch(state) {...} ✗ 4 methods ร— 4 states = 16 switch cases ✗ "HasMoney" logic scattered across 4 methods ✗ Add state โ†’ edit ALL methods ✗ Miss one case โ†’ inconsistent machine ✗ Can't test one state in isolation

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.

02
Section Two ยท The Pattern

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.

GoF Intent: “Allow an object to alter its behaviour when its internal state changes. The object will appear to change its class.”
— Gamma, Helm, Johnson, Vlissides (1994)
Analogy — a traffic light: A traffic light has three states: Red, Yellow, and Green. When the light is Green, cars go; when it’s Red, cars stop; when it’s Yellow, cars prepare to stop. The same light (context) behaves completely differently depending on which state it’s in. The light doesn’t have a big if/else checking its color — it delegates to the current state. When the timer expires, the state object itself triggers the transition to the next state: Green โ†’ Yellow โ†’ Red โ†’ Green. Each state knows what to do and what comes next. The light just says “tick()” and the current state handles everything.

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.

Each state is a class implementing a common State interface with all context methods
All behaviour for one state lives in one class. “What does HasMoney do?” โ†’ open HasMoneyState.java.
The context delegates every action to currentState.action()
The context has no conditionals. It just forwards: insertCoin() โ†’ state.insertCoin(this).
State classes trigger transitions by calling context.setState(new NextState())
Transitions happen from inside the state. Each state knows what comes next. The FSM is distributed across state classes.
Adding a new state = adding a new class, not editing existing code
Open/Closed Principle. Adding a “Maintenance” state means creating MaintenanceState — existing states and the context are unchanged.
03
Section Three ยท Anatomy

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).
State — UML Class Diagram
VendingMachine - state : VendingState + insertCoin() + selectProduct() + setState(VendingState) «interface» VendingState + insertCoin(ctx : VendingMachine) + selectProduct(ctx : VendingMachine) + dispense(ctx : VendingMachine) state IdleState + insertCoin(ctx) + selectProduct(ctx) HasMoneyState + insertCoin(ctx) + selectProduct(ctx) DispensingState + dispense(ctx) โ†’ ctx.setState(idle) State transitions (inside state classes): IdleState.insertCoin() โ†’ ctx.setState(HasMoney) HasMoneyState.select() โ†’ ctx.setState(Dispensing) DispensingState.dispense() โ†’ ctx.setState(Idle) ◆ filled diamond = composition (context owns state) ■ Blue = interface ■ Green = concrete state Context delegates to state; state transitions by calling ctx.setState()
State vs. Strategy โ€” same structure, different intent:
  • 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.
04
Section Four ยท How It Works

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.

Step 1 — Machine starts in IdleState. User calls machine.insertCoin()
Context delegates: state.insertCoin(this). IdleState accepts the coin and calls ctx.setState(new HasMoneyState()). Machine is now in HasMoney.
Step 2 — User calls machine.selectProduct()
Context delegates to HasMoneyState.selectProduct(). It validates the selection, then calls ctx.setState(new DispensingState(product)). Machine is now Dispensing.
Step 3DispensingState automatically delivers the product
DispensingState.dispense() delivers the item, decrements stock, and calls ctx.setState(stock > 0 ? new IdleState() : new OutOfStockState()).
Step 4 — User tries machine.selectProduct() while in IdleState
IdleState.selectProduct() prints “Insert coin first.” No transition. The state rejects invalid actions gracefully.
State — FSM Transition Diagram (Vending Machine)
Idle HasMoney Dispensing OutOfStock insertCoin() selectProduct() dispense() [stock > 0] dispense() [stock=0] refund() Each state class handles all actions and triggers the next transition. Context just delegates.
The pattern in pseudocode
// โ”€โ”€ Client interaction โ”€โ”€ VendingMachine vm = new VendingMachine(3); // 3 items in stock vm.insertCoin(); // ๐Ÿ’ฐ Coin accepted โ†’ state: HasMoney vm.selectProduct(); // โœ… Dispensing Cola โ†’ state: Dispensing โ†’ Idle vm.selectProduct(); // โš  Insert coin first (rejected โ€” we're in Idle) vm.insertCoin(); // ๐Ÿ’ฐ Coin accepted โ†’ state: HasMoney vm.insertCoin(); // โš  Already has money (rejected by HasMoneyState) vm.selectProduct(); // โœ… Dispensing Cola โ†’ state: Idle (2 left)
Behavioral patterns need both static UML and dynamic flow:
  • 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).
05
Section Five ยท Java Stdlib

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.

IN JAVA
Example 1 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).
Example 2 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.
Example 3 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.
Example 4 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.
Stdlib example — Thread state constraints
// Thread behaviour depends on its current state Thread t = new Thread(() -> System.out.println("Running")); System.out.println(t.getState()); // NEW t.start(); // NEW โ†’ RUNNABLE โœ“ System.out.println(t.getState()); // RUNNABLE (or TERMINATED) t.start(); // โœ— IllegalThreadStateException โ€” can't restart TERMINATED thread // Same method (start()), different behaviour depending on state
Enum states vs. State pattern:
  • The JDK mostly uses enum + switch for state machines (like Thread.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 + switch is perfectly fine.
06
Section Six ยท Implementation

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.

Java — State Pattern Vending Machine (core)
// โ”€โ”€ State interface โ”€โ”€ interface VendingState { void insertCoin(VendingMachine ctx); void selectProduct(VendingMachine ctx); void dispense(VendingMachine ctx); void refund(VendingMachine ctx); } // โ”€โ”€ Context: no conditionals, just delegates โ”€โ”€ class VendingMachine { private VendingState state; private int stock; public void setState(VendingState s) { this.state = s; } public void insertCoin() { state.insertCoin(this); } public void selectProduct() { state.selectProduct(this); } public void dispense() { state.dispense(this); } public void refund() { state.refund(this); } }
Who triggers the transition?:
  • 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.
07
Section Seven ยท Watch Out

Common Mistakes

Mistake #1 — Putting transition logic in the context instead of state classes: If the context has 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.
✗ Wrong — context manages transitions
// โœ— Context checks state type โ€” defeats the pattern public void selectProduct() { if (state instanceof HasMoneyState) { setState(new DispensingState()); // โ† belongs inside HasMoneyState } else { System.out.println("Can't select"); } }
✔ Correct — state manages its own transition
// โœ“ Context just delegates โ€” state decides everything public void selectProduct() { state.selectProduct(this); // โ† state handles + transitions }
Mistake #2 — Creating a new state object on every transition:
  • 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)).
Mistake #3 — State interface with too many methods:
  • If VendingState has 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 BaseState abstract 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+.
Mistake #4 — Confusing State with Strategy:
  • 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.
Mistake #5 — Circular state dependencies without clean separation:
  • If IdleState imports HasMoneyState which imports DispensingState which imports IdleState — a circular dependency.
  • Fix: All states depend only on the State interface and the Context. Create next states lazily or use a state factory/registry.
08
Section Eight ยท Decision Guide

When To Use State

Use State When
  • 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)
Avoid State When
  • 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
State vs. Confused Patterns
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
Decision Flowchart
Object behaviour changes based on internal state? No Not State Yes Transitions happen from inside (autonomously)? No Strategy (externally injected) Yes Many states, each with significant behaviour? No enum + switch (simple FSM) Yes State Pattern
09
Section Nine ยท Practice

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.
Interview Tip:
  • When asked about State, the interviewer wants to see: (1) a State interface with all context methods; (2) concrete state classes that implement all methods and trigger transitions via ctx.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.State as 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.