State Pattern: Changing Behavior When State Changes
In software, objects often behave differently depending on their current situation or state.
Hardcoding multiple
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:
- Pending (created but not paid)
- Paid (payment confirmed)
- Shipped (order on its way)
- Delivered (completed)
Instead of a giant
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
- Increased Class Count
- Each state requires its own class. For systems with many states, this can mean lots of files.
- Better than Conditionals
- Eliminates long if/elseblocks, improving maintainability and readability.
- Eliminates long
- Easy to Extend
- Adding a new state doesn’t require editing existing logic — just create a new state class.
- 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.