ReviseAlgo Logo

Behavioral Patterns

State

Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.

Last Updated: June 26, 2026 23 min read

The State Pattern is a behavioral design pattern that allows an object to alter its behavior when its internal state changes. By extracting state-specific behaviors into individual, polymorphic state classes, the context object can delegate actions dynamically, eliminating complex, nested conditional blocks and simulating class transitions at runtime.

1. Learning Objectives

  • Identify and refactor large conditional branches managing lifecycle transitions into decoupled state classes.
  • Analyze the trade-offs of transition ownership between the Context and the State subclasses.
  • Evaluate heap allocation differences between dynamic state instantiations and stateless shared flyweight singletons.
  • Compare the intent and structure of State, Strategy, and Command patterns.
  • Resolve circular header dependencies during state transitions in C++ and Java.

2. Problem & Naive Solution

Suppose you are building a controller for a commercial vending machine. The machine transitions through several states:

  • NO_COIN: Awaiting money.
  • HAS_COIN: Credit inserted, awaiting item selection.
  • SOLD: Dispensing the item.
  • OUT_OF_STOCK: Machine is empty.

The Naive Solution

A developer might implement this state machine using enum flags and conditional statements inside every action method:

This design introduces significant architectural issues:

  • Violates Open/Closed Principle: Adding a new state (e.g. PREVENTATIVE_MAINTENANCE or REFUND_PENDING) forces you to modify the conditionals in every action method (insertCoin, pressButton, dispense), risking bugs.
  • Complex Conditional Chains: As states and actions expand, methods become bloated with nested loops and branches, making them hard to read and debug.
  • Fragile Transition Invariants: Transition rules are scattered across conditionals, making it easy to miss updates or introduce invalid state paths.

3. Issues

Hardcoded lifecycle structures scale poorly. If transition rules depend on runtime checks (like item inventory counts or temperature sensors), the main controller becomes tightly coupled to sensor APIs, violating single responsibility rules.

4. Pattern Introduction & UML

The State Pattern addresses this by extracting state-specific behaviors into individual classes. The VendingMachine (Context) holds a reference to a VendingMachineState interface. When an action is called on the machine, it delegates execution to the active state class. State changes occur by replacing the active state reference with a different concrete state object.

UML: Vending Machine States

5. Participants

  • Context (VendingMachine): Defines the client interface, maintains a reference to the active state class, and holds inventory/cash balances.
  • State (VendingMachineState): The common interface declaring state-specific action methods.
  • Concrete State (NoCoinState, HasCoinState): Implements behaviors associated with a state and handles transition updates on the Context.

6. Theory (Transition Ownership & Comparison)

A key architectural decision in the State pattern is determining where transition logic lives:

  • Transition in States (Dynamic & Scalable): Subclasses (e.g. NoCoinState) trigger changes by calling context.setState(). - *Pros*: Adding a new state with custom transition paths is easy and doesn't require modifying the context class. - *Cons*: Concrete states must know about adjacent states, creating compile-time coupling between subclasses.
  • Transition in Context (Centralized Control): The context class evaluates return values from state actions and updates its active state reference accordingly. - *Pros*: Subclasses remain decoupled from one another. - *Cons*: The context becomes cluttered with state transition logic.

Pattern Comparisons

Pattern Decoupling Target State Transition Trigger
State Encapsulates state-specific behavior into classes. Subclasses change state dynamically at runtime.
Strategy Encapsulates algorithm implementations. Configured once by client during initialization.

7. Syntax Explanation

Managing state references requires careful design:

  • Java: Declares pre-instantiated, final state fields inside the context class to prevent heap allocation spikes.
  • Python: Leverages dynamic properties to change target methods at runtime without class casting overhead.
  • C++: Employs smart pointers (std::shared_ptr / forward declarations) to manage state references, resolving circular references between the context and state classes.

8. Step-by-Step Implementation

  1. Step 1: Create the State interface defining the action methods.
  2. Step 2: Build the Context class maintaining state variables and getter methods for concrete state instances.
  3. Step 3: Implement concrete state classes, accepting the Context instance in their constructor to trigger transitions.
  4. Step 4: Inside the concrete states, write the logic for each action and change states on the context when needed (e.g., calling context.setState()).
  5. Step 5: Instantiate the Context and invoke actions, verifying that behaviors and states update automatically.

9. Complete Code (Mini Project)

10. Code Walkthrough

Let's trace how the state-specific execution flows:

  • State Decoupling: The context class VendingMachine contains no state validation rules or conditional checks. It delegates actions (like insertCoin()) directly to currentState.insertCoin().
  • Encapsulated Transitions: The active state class (e.g. NoCoinState) triggers transitions by calling machine.setState(machine.getHasCoinState()). This encapsulates the state machine routing inside individual classes.
  • Circular Dependency Resolution: In the C++ example, forward declarations and reference pointers (VendingMachine&) decouple compile-time lookups between the context and state subclasses.

11. Execution Flow

  1. Insertion Trigger: Client calls insertCoin() on VendingMachine.
  2. Action Delegation: The context delegates the call to the active state (currentState.insertCoin()).
  3. State Transition: NoCoinState verifies the action, triggers transition calls to machine.setState(), and updates the current state to HasCoinState.
  4. Selection Broadcast: Client calls selectProduct(). The call is routed to HasCoinState, which transitions to SoldState and executes dispense().

12. Internal Working (Object Allocation vs. Shared States)

Managing state allocations has performance and memory implications:

  • Dynamic State Allocation Churn: If state transitions construct new state instances (e.g. machine.setState(new HasCoinState(machine))), it results in frequent allocations and garbage collection churn.
  • Shared State Singletons (Recommended): To optimize memory, instantiate all state objects once inside the context constructor (as shown in the Java example) and reuse them during transitions. If state objects contain no instance variables, they can be shared globally (as Flyweights) across multiple contexts.

13. Complexity Analysis

  • Time Complexity: $O(1)$ constant time overhead to resolve dynamic state method dispatch.
  • Space Complexity: $O(S)$ where $S$ is the total count of concrete state classes pre-allocated inside the context.

14. Best Practices

  • Pre-Allocate States: Store concrete state instances as final fields inside the context class to prevent unnecessary heap allocation churn.
  • Prefer Stateless State Classes: Keep state classes stateless. Pass the context reference into method calls (e.g. insertCoin(VendingMachine context)) so you can share state instances across contexts.

15. Common Mistakes

  • Leaking State References to Clients: Allowing clients to modify state variables directly, bypassing the context's transition rules.
  • Deep Subclass Coupling: Direct instantiation of adjacent states inside subclass methods (e.g. writing setState(new HasCoinState()) inside NoCoinState instead of using machine.getHasCoinState()), which creates compile-time dependencies.

16. Framework Usage

  • Spring State Machine: A robust framework for building state machines in Spring, supporting hierarchical states, guards, and transition metrics.
  • TCP Connection Protocols: Low-level operating system network stacks use state patterns to manage TCP socket connections (e.g. LISTEN, SYN_SENT, ESTABLISHED, CLOSED).

17. Interview Discussion

Q: Who should trigger state transitions: the Context or the State subclasses?
Answer: - State Subclasses: Triggering transitions inside states is preferred for dynamic state paths, but couples the subclasses together. - Context Class: Triggering transitions inside the context keeps subclasses decoupled, but can clutter the context class with transition logic.
Q: How do you handle circular dependencies between the Context and State classes in C++?
Answer: Use Forward Declarations in header files. Declare the VendingMachine class before defining the VendingMachineState interface, and implement the transition methods in a separate .cpp implementation file.
Q: What is the main difference between the State and Strategy patterns?
Answer: - State: The context changes behavior dynamically based on changes to its internal state. The state classes manage transitions automatically. - Strategy: The client configures the context with a specific algorithm (strategy) once during initialization, and transitions rarely occur during execution.

18. Practice Exercises

  • Easy: Write a Python program containing a simple MediaPlayer state machine with PlayState and PauseState behaviors.
  • Medium: Design a ThreadScheduler simulating standard thread lifecycles (NEW, RUNNABLE, BLOCKED, TERMINATED) using the State pattern.
  • Hard: Build an e-commerce document editor supporting transition routes (Draft -> Moderation -> Published) with permission checks.

19. Challenge Problem

Design an ATM Transaction Processing System. The ATM transitions through several states: IdleState (waiting for card), PinEntryState (verifying pin), CashWithdrawalState (dispensing cash), and OutofServiceState (out of cash). The controller must support card entry, pin validation, cash dispensing, and cancellations. Write this state controller in Java, Python, or C++ and verify transitions including card ejection on invalid pins.

20. Summary & Cheat Sheet

  • State extracts state-specific behavior into individual classes, removing nested conditionals.
  • State transitions occur by updating the state reference pointer inside the context.
  • Pre-allocate state objects in the context constructor to avoid memory churn.
  • Use forward declarations in C++ to resolve circular reference dependencies.

21. Quiz

1. What is the primary purpose of the State design pattern?

A) To adapt incompatible interfaces
B) To allow an object to alter its behavior when its internal state changes (Correct)
C) To control resource allocation

2. Which pattern is structurally identical to the State pattern but differs in intent?

A) Command Pattern
B) Strategy Pattern (Correct)
C) Proxy Pattern

3. What is a drawback of triggering state transitions inside the concrete state classes?

A) Disables compiler code sharing
B) Couples concrete state classes to one another (Correct)
C) Increases vtable dynamic dispatch overhead

4. How do you prevent allocating new state objects on the heap during transitions?

A) Enforce single-threaded execution
B) Instantiate state objects once inside the context constructor and reuse them (Correct)
C) Store state fields as local primitive numbers

5. Which of the following is a classic example of a state machine in low-level OS networking?

A) TCP Socket Connection states (Correct)
B) DNS query resolving
C) File write logs

6. What design rule is violated by checking instanceof on active state references inside client classes?

A) Single Responsibility Principle
B) Liskov Substitution Principle / Encapsulation (Correct)
C) Interface Segregation Principle

7. What is the time complexity of delegating actions to the active state?

A) $O(\log N)$
B) $O(1)$ (Correct)
C) $O(N)$

8. How can state classes be shared across multiple contexts (Flyweights)?

A) By declaring them as final classes
B) By keeping state classes completely stateless and passing the context reference into their methods (Correct)
C) By compiling them as native assemblies

9. In C++, how do you resolve circular dependency compile errors between Context and State?

A) Define all functions as inline static templates
B) Use forward declarations (class VendingMachine;) and compile implementations in separate source files (Correct)
C) Avoid using classes and use structs

10. Does the State pattern support the Open/Closed Principle?

A) Yes, because you can introduce new state classes without modifying the context class (Correct)
B) No, because adding states forces rewriting context connection pools
C) Only when using Spring State Machine framework

22. Next Lesson Preview

In the next lesson, we will explore the Template Method Pattern. We will learn how to define the skeleton of an algorithm in a base class, letting subclasses override specific steps without changing the algorithm's structure!