1. The Problem
Problem: Behavior Changes Based on Internal State
Some systems behave differently depending on their current state. Example: a document lifecycle — Draft → Moderation → Published.
A naïve implementation encodes state as conditionals:
if state == "draft":
...
elif state == "moderation":
...
elif state == "published":
...
As states grow, this leads to:
- Branching scattered across methods
- State transitions hard-coded and duplicated
- High risk when adding new states
- Violations of Open/Closed and Single Responsibility
The core issue: state-dependent behavior is mixed with control logic.
We need behavior to change automatically when state changes — without if/else.
2. The State Pattern: Behavior Is a Function of State
The State Pattern solves this by making state explicit as an object.
Core idea:
- Model each state as a class
- Encapsulate state-specific behavior
- Delegate behavior to the current state
- Switch state by replacing the state object
Instead of asking “what state am I in?”, the context asks the state to act.
A system using State has two parts:
- State — an interface defining behavior
- Context — holds a reference to the current state
The context does not branch on state. The state controls behavior and transitions.
You get:
- No state conditionals
- Localized behavior per state
- Explicit state transitions
- Easy extensibility
Use State when:
- Object behavior changes based on internal state
- State transitions are complex or growing
- You see
if state == Xeverywhere - You want to add states without touching existing logic
3. Implementation: State + Context in Python
State interface
from abc import ABC, abstractmethod
class DocumentState(ABC):
@abstractmethod
def publish(self, document):
pass
Concrete states
class DraftState(DocumentState):
def publish(self, document):
document.set_state(ModerationState())
return "Document moved to moderation"
class ModerationState(DocumentState):
def publish(self, document):
document.set_state(PublishedState())
return "Document published"
class PublishedState(DocumentState):
def publish(self, document):
return "Document is already published"
Each state:
- Knows what it can do
- Decides the next state
- Owns its own behavior
Context
class Document:
def __init__(self):
self._state = DraftState()
def set_state(self, state: DocumentState):
self._state = state
def publish(self):
return self._state.publish(self)
Usage
doc = Document()
doc.publish() # Draft → Moderation
doc.publish() # Moderation → Published
doc.publish() # No-op
No conditionals. Behavior changes by swapping the state object.
4. Strategy vs State (Critical Distinction)
They look similar structurally. They are not conceptually the same.
| Aspect | Strategy | State |
|---|---|---|
| Purpose | Choose algorithm | Model state-dependent behavior |
| Who switches? | Client / external | State itself |
| Trigger | Configuration / runtime choice | Internal transitions |
| Focus | How to do something | What is allowed now |
Rule of thumb:
- Strategy → “Which algorithm should I use?”
- State → “What can I do right now?”
If transitions matter → State. If algorithms are interchangeable → Strategy.
5. When to Use State (and When Not To)
You likely need State when you notice:
- “Behavior depends on current status”
- “Valid actions change over time”
- “State transitions are scattered”
- “Adding a new state breaks many places”
Common pitfalls:
- Too many states → missing higher-level abstraction
- States mutating context data excessively → leaking responsibilities
- States knowing too much about each other → tight coupling
- Using State when a single flag would do → overengineering
Avoid State if:
- Behavior differences are trivial
- States don’t affect behavior meaningfully
- Transitions are static and minimal