State Pattern
Allow an object to alter its behavior when its internal state changes. A behavioral design pattern for finite state machines.
Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.
State Pattern
Overview
The State Pattern is a behavioral design pattern that lets an object alter its behavior when its internal state changes. Instead of large switch statements, each state is encapsulated in its own class with state-specific behavior.
When to Use
Use the State Pattern when:
- An object’s behavior depends on its state and must change at runtime
- You have large conditional statements that switch behavior based on state
- States have complex transitions with entry/exit actions
- You want to add new states without modifying existing state classes
Solution
Python
from abc import ABC, abstractmethod
class OrderState(ABC):
@abstractmethod
def pay(self, order):
pass
@abstractmethod
def ship(self, order):
pass
@abstractmethod
def cancel(self, order):
pass
class PendingState(OrderState):
def pay(self, order):
print("Processing payment...")
order.transition_to(PaidState())
def ship(self, order):
print("Cannot ship: payment pending")
def cancel(self, order):
print("Order cancelled")
order.transition_to(CancelledState())
class PaidState(OrderState):
def pay(self, order):
print("Already paid")
def ship(self, order):
print("Shipping order...")
order.transition_to(ShippedState())
def cancel(self, order):
print("Issuing refund...")
order.transition_to(CancelledState())
class ShippedState(OrderState):
def pay(self, order):
print("Already paid")
def ship(self, order):
print("Already shipped")
def cancel(self, order):
print("Cannot cancel: already shipped")
class CancelledState(OrderState):
def pay(self, order):
print("Cannot pay: order cancelled")
def ship(self, order):
print("Cannot ship: order cancelled")
def cancel(self, order):
print("Already cancelled")
class Order:
def __init__(self):
self._state = PendingState()
def transition_to(self, state):
self._state = state
def pay(self):
self._state.pay(self)
def ship(self):
self._state.ship(self)
def cancel(self):
self._state.cancel(self)
# Usage
order = Order()
order.pay()
order.ship()
order.cancel() # Cannot cancel: already shipped
JavaScript
class Order {
constructor() { this.state = new PendingState(this); }
transitionTo(state) { this.state = state; }
pay() { this.state.pay(); }
ship() { this.state.ship(); }
cancel() { this.state.cancel(); }
}
class PendingState {
constructor(order) { this.order = order; }
pay() {
console.log("Processing payment...");
this.order.transitionTo(new PaidState(this.order));
}
ship() { console.log("Cannot ship: payment pending"); }
cancel() {
console.log("Order cancelled");
this.order.transitionTo(new CancelledState(this.order));
}
}
class PaidState {
constructor(order) { this.order = order; }
pay() { console.log("Already paid"); }
ship() {
console.log("Shipping order...");
this.order.transitionTo(new ShippedState(this.order));
}
cancel() {
console.log("Issuing refund...");
this.order.transitionTo(new CancelledState(this.order));
}
}
class ShippedState {
constructor(order) { this.order = order; }
pay() { console.log("Already paid"); }
ship() { console.log("Already shipped"); }
cancel() { console.log("Cannot cancel: already shipped"); }
}
class CancelledState {
constructor(order) { this.order = order; }
pay() { console.log("Cannot pay: order cancelled"); }
ship() { console.log("Cannot ship: order cancelled"); }
cancel() { console.log("Already cancelled"); }
}
const order = new Order();
order.pay();
order.ship();
order.cancel(); // Cannot cancel: already shipped
Java
interface OrderState {
void pay(Order order);
void ship(Order order);
void cancel(Order order);
}
class PendingState implements OrderState {
public void pay(Order order) {
System.out.println("Processing payment...");
order.transitionTo(new PaidState());
}
public void ship(Order order) {
System.out.println("Cannot ship: payment pending");
}
public void cancel(Order order) {
System.out.println("Order cancelled");
order.transitionTo(new CancelledState());
}
}
class PaidState implements OrderState {
public void pay(Order order) {
System.out.println("Already paid");
}
public void ship(Order order) {
System.out.println("Shipping order...");
order.transitionTo(new ShippedState());
}
public void cancel(Order order) {
System.out.println("Issuing refund...");
order.transitionTo(new CancelledState());
}
}
class ShippedState implements OrderState {
public void pay(Order order) {
System.out.println("Already paid");
}
public void ship(Order order) {
System.out.println("Already shipped");
}
public void cancel(Order order) {
System.out.println("Cannot cancel: already shipped");
}
}
class CancelledState implements OrderState {
public void pay(Order order) {
System.out.println("Cannot pay: order cancelled");
}
public void ship(Order order) {
System.out.println("Cannot ship: order cancelled");
}
public void cancel(Order order) {
System.out.println("Already cancelled");
}
}
class Order {
private OrderState state = new PendingState();
public void transitionTo(OrderState state) {
this.state = state;
}
public void pay() { state.pay(this); }
public void ship() { state.ship(this); }
public void cancel() { state.cancel(this); }
}
// Usage
Order order = new Order();
order.pay();
order.ship();
order.cancel(); // Cannot cancel: already shipped
Explanation
The State Pattern involves two roles:
- Context (
Order): Maintains a reference to the current state and delegates behavior to it - State Interface (
OrderState): Defines the contract for state-specific behavior - Concrete States (
PendingState,PaidState, etc.): Implement behavior specific to each state and handle transitions
Variants
| Variant | Description | Use Case |
|---|---|---|
| Simple State Machine | States handle transitions | Small number of states |
| Hierarchical State Machine | States can have substates | Complex nested behavior |
| State Table | Transitions stored in a lookup table | Many states with predictable transitions |
| Pushdown Automaton | Stack-based state history | Undo/reversible state transitions. See Memento |
What Works
- Delegate all state-dependent behavior to state objects, keeping the context thin
- Make state objects immutable or recreate them on transition to avoid shared state bugs
- Use enums for well-defined states in languages that support them (Java, TypeScript)
- Add entry/exit hooks when transitions need side effects (logging, analytics)
- Consider a transition table when you have many states and transitions become hard to manage
Common Mistakes
- Duplicating transition logic across multiple state classes instead of centralizing it
- Allowing the context to modify state directly instead of delegating to the current state
- Forgetting to handle invalid transitions gracefully, leading to runtime errors
- Creating circular state transitions that can cause infinite loops
- Mixing state logic with business logic, making the pattern hard to test
Frequently Asked Questions
Q: What is the difference between State and Strategy? A: Both encapsulate behavior in interchangeable objects. State is about changing behavior based on internal state transitions. Strategy is about selecting an algorithm from the outside. The intent differs: State manages lifecycle, Strategy provides options.
Q: Should states know about each other? A: In the classic implementation, states trigger transitions to other states. An alternative is to let the context or a transition table manage transitions, keeping states decoupled.
Q: Can I use enums instead of classes for simple state machines? A: Yes. For simple cases, an enum with a switch statement is sufficient. Use the full State Pattern when states have complex behavior or you expect frequent additions.
Is this pattern suitable for small projects?
For small projects with few components, this pattern may add unnecessary complexity. Start simple and introduce the pattern when you feel the pain it solves.
How does this pattern compare to alternatives?
Each pattern makes different trade-offs. Review the variants table above and consider your specific constraints: team size, performance requirements, and future scaling plans.
Can I partially apply this pattern?
Yes. Many teams adopt patterns incrementally. Start with the core idea and add sophistication as needed. The pattern is a guide, not a strict blueprint.
Related Resources
Strategy Pattern
Define a family of algorithms, encapsulate each one, and make them interchangeable. A behavioral design pattern for flexible behavior selection.
PatternCommand Pattern
Encapsulate a request as an object, letting you parameterize clients with queues, logs, and undoable operations. A behavioral design pattern.
PatternObserver Pattern
Define a subscription mechanism to notify multiple objects about events. A behavioral design pattern for event-driven communication.
PatternMediator Pattern
Define an object that encapsulates how a set of objects interact. A behavioral design pattern for reducing chaotic dependencies.
PatternMemento Pattern
Capture and restore an object's internal state without violating encapsulation. A behavioral design pattern for undo/redo.