Logo Sujal Magar
State Pattern

State Pattern

February 19, 2026
5 min read
Table of Contents

1. Introduction

The State pattern (also known as Objects for States) is one of the most widely used Behavioral Design Patterns. It allows an object to alter its behavior when its internal state changes, making it appear as though the object’s class has changed. This pattern is especially powerful in systems with complex conditional logic based on state, encapsulating state-specific behaviors into separate classes for better maintainability and extensibility, while keeping the client code simple and unaware of state transitions.

It enables adherence to the Open-Closed Principle (open for extension, closed for modification) and helps eliminate massive if-else switch statements by delegating behavior to polymorphic state objects.


2. Code Example

Bad Approach

Hardcoded Conditional Creation (Tight Coupling & Violation of OCP)

class Order:
  def __init__(self):
    sel.status = "new"  # Initial state
 
  def ship(self):
    if self.status == "new":
      self.status = "shipped"
      return "Order shipped"
    elif self.status == "shipped":
      return "Order already shipped"
    elif self.status == "delivered":
      return "Order already delivered"
    else:
      raise ValueError("Invalid status")
 
  def deliver(self):
    if self.status == "shipped":
      self.status = "delivered":
      return "Order delivered"
    elif self.status == "new":
      return "Cannot deliver without shipping"
    elif self.status == "delivered":
      return "Order already delivered"
    else:
      raise ValueError("Invalid status")
 
# Client code - fragile and violates open-closed principle
order = Order()
print(order.ship())     # Order shipped
print(order.deliver())  # Order delivered

Problems:

  • Every new state or action requires modifying multiple methods with growing if-elif chains
  • High cyclomatic complexity and duplicated state checks across methods
  • Client indirectly coupled to all states; adding states breaks existing code

Good Approach

State Pattern (Encapsulated State Behaviors)

from abc import ABC, abstractmethod
 
# State interface
class OrderState(ABC):
  @abstractmethod
  def ship(self, order):
    pass
 
  @abstractmethod
  def deliver(self, order):
    pass
 
# Concrete states
class NewState(OrderState):
  def ship(self, order):
    order.set_state(ShippedState())
    return "Order shipped"
 
  def deliver(self, order):
    return "Cannot deliver without shipping"
 
class ShippedState(OrderState):
  def ship(self, order):
    return "Order already shipped"
 
  def deliver(self, order):
    order.set_state(DeliveredState())
    return "Order delivered"
 
class DeliveredState(OrderState):
  def ship(self, order):
    return "Order already delivered"
 
  def deliver(self, order):
    return "Order already delivered"
 
# Context-manages the current state
class Order:
  def __init__(self):
    self._state = NewState()  # Initial state
 
  def set_state(self, state: OrderState):
    self._state = state
 
  def deliver(self):
    return self._state.deliver(self)
 
# Client - depends only on abstraction
order = Order()
 
print(order.ship())     # Order shipped
print(order.deliver())  # Order delivered

Adding a new state (e.g., CancelledState) or action (e.g., cancel) requires only creating a new state class and updating the interface (no changes to existing Order class or client code).


3. Complexities & Coupling Reduced/Solved

ProblemHow State HelpsBenefit Level
Tight coupling to state-specific logicBehaviors delegated to swappable state objects; context uses interfaceHigh
Large conditional blocks (if-elif/switch)Replaced by polymorphic calls in state classesHigh
Violation of Open-Closed PrincipleNew states/behaviors added via new classes without modifying contextVery High
Duplicated state checksEach state centralizes its own transitions and actionsMedium-High
Difficult testabilityStates can be mocked or tested independentlyHigh
High cyclomatic complexityDistributes complexity across small state classesHigh

4. When to Use State

  • An object’s behavior depends on its state and changes at runtime (e.g., finite state machines)
  • To avoid monolithic classes with massive conditionals for state handling
  • Systems with many states and transitions (e.g., workflows, protocols)
  • When states can be hierarchical or composed (advaned variants)
  • To make state logic extensible without altering the main class

Common real-world examples:

  • Order processing (new, shipped, delivered, cancelled)
  • Vending machines (no coin, has coin, dispensing)
  • TCP connections (listen, established, closed)
  • Game character states (idle, running, jumping)

5. When Not to Use State

Avoid when:

  • States are few and simple (basic if-else is sufficient and clearer)
  • No runtime state changes or fixed behavior (prefer Strategy for algorithms)
  • The system is small where extra classes add unnecessary overhead
  • States don’t affect behavior significantly (use enums or flags)
  • Performance is critical and polymorphism overhead is an issue (rare)
  • Overlapping with Strategy (if no internal state transitions; use for pure algorithms)
  • You are tempted to use it just because “it’s a behavioral pattern” (this leads to over-abstraction)