7 min read
Object-Oriented Design Index
Tier 1 -- Foundations
Core Concepts
SOLID Principles
Creational Patterns
Structural Patterns
Tier 2
Tier 3
Tier 4
Tier 5
Object-Oriented Design Index
Tier 1 -- Foundations
Core Concepts
SOLID Principles
Creational Patterns
Structural Patterns
Tier 2
Tier 3
Tier 4
Tier 5
State Pattern
1. What Is It?
The pattern allows an object to alter its behavior when its internal changes. The object will appear to change its class. Each state is encapsulated in its own class, and the context delegates behavior to the current state object.
Without it, state-dependent behavior ends up as a tangle of if/elif/switch blocks inside a single class — every method that cares about state must check it, every new state requires editing every method. The class grows proportionally to states × transitions × behaviors. The State pattern externalizes each state into its own class, making transitions explicit and behavior self-contained.
A media player class handles playback differently depending on whether it is in a Playing, Paused, or Stopped state. Currently, every method like play(), pause(), and stop() begins with a series of if/elif checks against a current_state variable. A fourth state, Buffering, needs to be added. What is the primary maintenance problem with this approach?
2. How It Works
Participants:
- — declaring the behavior methods that vary by
- ConcreteState — implements behavior for a specific state; may also trigger state transitions by calling
context.set_state() - Context — holds a reference to the current State object; delegates method calls to it; exposes
set_state()for transitions
Step-by-step:
- Identify the state-dependent behaviors (the methods that have
if state == X: ...logic) - Extract a
with those methods - Create a
ConcreteStateclass for each state — each implements all State methods with the right behavior for that state - Context delegates to
current_state.method()— no conditionals needed - Transitions: when a state's method determines the next state, it calls
context.set_state(NextState())
Python:
from __future__ import annotations from abc import ABC, abstractmethod class VendingMachineState(ABC): @abstractmethod def insert_coin(self, context: VendingMachine) -> None: pass @abstractmethod def select_item(self, context: VendingMachine) -> None: pass @abstractmethod def dispense(self, context: VendingMachine) -> None: pass class IdleState(VendingMachineState): def insert_coin(self, context: VendingMachine) -> None: print("Coin inserted.") context.set_state(CoinInsertedState()) def select_item(self, context: VendingMachine) -> None: print("Please insert a coin first.") def dispense(self, context: VendingMachine) -> None: print("Please insert a coin first.") class CoinInsertedState(VendingMachineState): def insert_coin(self, context: VendingMachine) -> None: print("Coin already inserted.") def select_item(self, context: VendingMachine) -> None: print("Item selected.") context.set_state(ItemSelectedState()) def dispense(self, context: VendingMachine) -> None: print("Please select an item first.") class ItemSelectedState(VendingMachineState): def insert_coin(self, context: VendingMachine) -> None: print("Please wait — dispensing in progress.") def select_item(self, context: VendingMachine) -> None: print("Item already selected.") def dispense(self, context: VendingMachine) -> None: print("Dispensing item. Enjoy!") context.set_state(IdleState()) class VendingMachine: def __init__(self) -> None: self._state: VendingMachineState = IdleState() def set_state(self, state: VendingMachineState) -> None: self._state = state def insert_coin(self) -> None: self._state.insert_coin(self) def select_item(self) -> None: self._state.select_item(self) def dispense(self) -> None: self._state.dispense(self) # Usage machine = VendingMachine() machine.select_item() # Please insert a coin first. machine.insert_coin() # Coin inserted. machine.insert_coin() # Coin already inserted. machine.select_item() # Item selected. machine.dispense() # Dispensing item. Enjoy! machine.dispense() # Please insert a coin first.
Java:
// State interface public interface VendingMachineState { void insertCoin(VendingMachine context); void selectItem(VendingMachine context); void dispense(VendingMachine context); } // Context public class VendingMachine { private VendingMachineState currentState; public VendingMachine() { this.currentState = new IdleState(); } public void setState(VendingMachineState state) { this.currentState = state; } public void insertCoin() { currentState.insertCoin(this); } public void selectItem() { currentState.selectItem(this); } public void dispense() { currentState.dispense(this); } } // ConcreteState: Idle public class IdleState implements VendingMachineState { @Override public void insertCoin(VendingMachine context) { System.out.println("Coin inserted."); context.setState(new CoinInsertedState()); } @Override public void selectItem(VendingMachine context) { System.out.println("Please insert a coin first."); } @Override public void dispense(VendingMachine context) { System.out.println("Please insert a coin first."); } } // ConcreteState: CoinInserted public class CoinInsertedState implements VendingMachineState { @Override public void insertCoin(VendingMachine context) { System.out.println("Coin already inserted."); } @Override public void selectItem(VendingMachine context) { System.out.println("Item selected."); context.setState(new ItemSelectedState()); } @Override public void dispense(VendingMachine context) { System.out.println("Please select an item first."); } } // ConcreteState: ItemSelected public class ItemSelectedState implements VendingMachineState { @Override public void insertCoin(VendingMachine context) { System.out.println("Please wait — dispensing in progress."); } @Override public void selectItem(VendingMachine context) { System.out.println("Item already selected."); } @Override public void dispense(VendingMachine context) { System.out.println("Dispensing item. Enjoy!"); context.setState(new IdleState()); } }
In the State pattern, when a ConcreteState's method determines that the system should move to a new state, how is that transition typically triggered?
3. Variants & Comparisons
| Approach | How It Works | Pros | Cons | Best For |
|---|---|---|---|---|
| State Pattern | Each state is a class; transitions via context.setState() | OCP-compliant, each state isolated, easy to add states | More classes, object allocation per transition | Complex state machines with distinct per-state behavior |
| Enum + Switch | State stored as enum; switch in every method | Simple, all logic in one file | Violates OCP, methods grow with states | Small, stable state machines (2–3 states) |
| State Table / Map | Explicit {state: {event: (action, nextState)}} table | Very clear transition model | Less flexible for complex actions | Finite automata, protocol parsers |
Who triggers transitions:
- -driven: ConcreteState calls
context.set_state()— knows its successors - Context-driven: Context holds transition logic — states are pure behavior, no knowledge of others
State-driven is more common and more encapsulated; Context-driven is easier to visualize transitions in one place.
A team is building a backend service that manages TCP connection states (e.g., CLOSED, LISTEN, SYN_RECEIVED, ESTABLISHED, FIN_WAIT). The states follow well-defined transition rules, and the primary concern is having a clear, readable model of which events trigger which transitions. Which approach is the best fit for this use case?
4. When to Use It (and When NOT To)
Use when:
- An object's behavior varies significantly based on its , and that behavior can be modeled as a finite set of distinct states
- You have growing
if/elif/switchblocks gated on a variable in multiple methods - State transitions are complex and you want them explicit
Don't use when:
- The state machine has only 2–3 states that won't grow — a simple enum + switch is clearer
- State-specific behavior is trivial (one line) — the overhead of extra classes isn't justified
Decision triggers:
- "Every method in this class starts with
if self.state == X: ..." → State pattern - "Adding a new state requires editing 5 methods" → State pattern
- "I have a document that can be Draft, Under Review, Approved, or Archived" → State pattern
Anti-patterns:
- State explosion: Too many states → too many classes. Consider whether some states can be combined or whether a state table is clearer.
- Putting transition logic in Context: Context becomes a god object managing all transitions. Let states manage their own successors where possible.
A team is building an e-commerce order management system. The Order class has methods like process(), cancel(), and ship(), and every one of these methods begins with a block like if self.status == 'pending': ... elif self.status == 'paid': ... elif self.status == 'shipped': .... Adding a new order status (e.g., 'backordered') requires updating all five methods. Which design decision is most appropriate here?
5. Real-World Usage
1. TCP connection states: A TCP connection moves through CLOSED → SYN_SENT → ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED. Each handles incoming packets differently — receive(SYN) in CLOSED starts a handshake; in ESTABLISHED state it's an error. State pattern maps directly to this model.
2. Order management systems: An Order has states: PENDING → CONFIRMED → SHIPPED → DELIVERED → CANCELLED. Each state defines which operations are valid (ship() is only valid in CONFIRMED; cancel() is invalid after SHIPPED). State pattern prevents invalid transitions without giant if blocks.
3. Java thread states: A Java Thread has states NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED defined by Thread.State enum. The JVM's internal thread scheduler behaves like a State machine — each state determines which operations (start, sleep, join, interrupt) are valid.
An e-commerce platform models orders with the states: PENDING → CONFIRMED → SHIPPED → DELIVERED. A developer needs to implement a cancel() operation. Using the State pattern, what is the most appropriate way to handle calling cancel() on an order that is already in the SHIPPED state?
6. Interview Cheat Sheet
Key sentences:
- " replaces -conditional logic scattered across methods with — each state is a class that implements the full behavior for that state."
- "The key benefit is Open/Closed: adding a new state means adding a new class, not editing every existing method."
- "There are two transition models: state-driven (ConcreteState calls
context.setState()) and context-driven (Context holds the transition table). State-driven is more encapsulated." - "State and look identical structurally — both delegate behavior to an encapsulated object. The difference is intent: State transitions internally based on object lifecycle; is injected externally by the client."
Common follow-up questions:
Q: How is State different from Strategy? A: Structurally nearly identical. The difference is semantic and in who drives changes: State transitions happen based on the object's own logic (lifecycle-driven); Strategy is chosen and injected by the client. A Strategy doesn't know about other Strategies; a State typically knows its successor states.
Q: Who should own the transition logic — the state or the context? A: Either can work. State-driven is more encapsulated and keeps transition logic close to the state that triggers it. Context-driven makes all transitions visible in one place. I'd choose state-driven for complex systems and context-driven when a visual state table is needed for clarity.
Q: How do you test state machines?
A: Unit-test each ConcreteState independently by creating it directly and calling its methods with a mock Context. Test transitions by asserting context.setState() was called with the expected next state.
Connections:
- State vs. Strategy → same structure, different intent and transition ownership
- State + → Commands can trigger state transitions
- State is the object-oriented formalization of a Finite State Machine (FSM)
Glossary History
Click dotted jargon to save explanations here.
Glossary History
Click dotted jargon to save explanations here.