State Pattern: Changing Behavior When State Changes
By Misbahul Munir3 min read672 words

State Pattern: Changing Behavior When State Changes

Backend Development
design pattern
behavioral pattern

In software, objects often behave differently depending on their current situation or state.

Hardcoding multiple

if/else
or
switch
statements for every possible condition is messy, hard to maintain, and violates the Open/Closed Principle.

The State Pattern solves this by encapsulating each possible state into its own class, letting an object change its behavior dynamically at runtime without huge conditional blocks.


Real-World Analogy

Think of a vending machine:

  • If it’s Idle, pressing a button won’t do anything until you insert money.
  • If it’s HasMoney, pressing a button will dispense the product.
  • If it’s OutOfStock, pressing a button will just show a message.

The vending machine changes its behavior based on its current state — you don’t rewrite the machine’s entire logic; you just change the state it’s in.


Real-World Backend Example

Let’s say you’re building a backend order system for an e-commerce platform.

An order can be:

  1. Pending (created but not paid)
  2. Paid (payment confirmed)
  3. Shipped (order on its way)
  4. Delivered (completed)

Instead of a giant

if/else
in the
Order
class, we use the State Pattern.

State Interface

from abc import ABC, abstractmethod class OrderState(ABC): @abstractmethod def next(self, order): pass @abstractmethod def cancel(self, order): pass @abstractmethod def __str__(self): pass

Concrete States

class PendingState(OrderState): def next(self, order): print("Order is now Paid.") order.state = PaidState() def cancel(self, order): print("Order canceled from Pending state.") order.state = CanceledState() def __str__(self): return "Pending" class PaidState(OrderState): def next(self, order): print("Order is now Shipped.") order.state = ShippedState() def cancel(self, order): print("Refund issued. Order canceled.") order.state = CanceledState() def __str__(self): return "Paid" class ShippedState(OrderState): def next(self, order): print("Order is now Delivered.") order.state = DeliveredState() def cancel(self, order): print("Cannot cancel, order already shipped!") def __str__(self): return "Shipped" class DeliveredState(OrderState): def next(self, order): print("Order already delivered. No next state.") def cancel(self, order): print("Cannot cancel, order already delivered!") def __str__(self): return "Delivered" class CanceledState(OrderState): def next(self, order): print("Order was canceled. No next state.") def cancel(self, order): print("Order already canceled.") def __str__(self): return "Canceled"

Context

class Order: def __init__(self): self.state = PendingState() def next_state(self): self.state.next(self) def cancel_order(self): self.state.cancel(self) def __str__(self): return f"Order is currently: {self.state}"

Example usage

if __name__ == "__main__": order = Order() print(order) order.next_state() # Pending -> Paid print(order) order.next_state() # Paid -> Shipped print(order) order.cancel_order() # Cannot cancel shipped order order.next_state() # Shipped -> Delivered print(order)

Output:

Order is currently: Pending Order is now Paid. Order is currently: Paid Order is now Shipped. Order is currently: Shipped Cannot cancel, order already shipped! Order is now Delivered. Order is currently: Delivered

Gotchas & Considerations

  1. Increased Class Count
    • Each state requires its own class. For systems with many states, this can mean lots of files.
  2. Better than Conditionals
    • Eliminates long
      if/else
      blocks, improving maintainability and readability.
  3. Easy to Extend
    • Adding a new state doesn’t require editing existing logic — just create a new state class.
  4. Clearer Transitions
    • You control exactly which transitions are allowed between states.

When to Use State Pattern

  • Objects need to change behavior dynamically based on their current status.
  • You want to avoid bloated conditional logic.
  • Each state has distinct and isolated logic.

Final Thoughts

The State Pattern is a clean, extensible way to model objects whose behavior changes over time. It enforces separation of concerns, makes transitions explicit, and removes messy branching logic.

However, it comes at the cost of more classes and potentially more files. For small state machines, a simple conditional might be fine. But for larger, evolving systems — like workflow engines, order processing, or authentication flows — the State Pattern keeps your code maintainable and future-proof.

When applied correctly, it turns a tangled web of conditions into a well-structured, pluggable system of behaviors.