State
Let an object change its behaviour when its internal state changes, by delegating to a state object instead of switching on a status field.
Big idea
State turns each state of an object into its own class. The object (the context) holds the current state object and forwards every action to it; the state decides what the action means in that state and which state comes next. A wall of if status == … checks becomes a set of small classes, each describing one situation.
The problem it solves
An article moves through draft → in review → published → archived. Every method checks the status:
def publish(self):
if self.status == "draft": raise ...
elif self.status == "review":
if self.user.is_editor: self.status = "published"
elif self.status == "published": ...
edit(), submit(), reject(), archive() each have the same ladder. Adding a "scheduled" state means editing every method, and it's easy to miss one — say, letting someone edit an archived article.
Think of it like
A traffic light's controller. Pressing the pedestrian button means something different on red (nothing — already stopping), on green (start the countdown to amber), and on amber (wait). The button is the same; the meaning comes from the current state, and each state knows which one follows it.
Structure
In code
class DocState:
name = "?"
def submit(self, doc): return f"can't submit while {self.name}"
def approve(self, doc): return f"can't approve while {self.name}"
def reject(self, doc): return f"can't reject while {self.name}"
def edit(self, doc, text): return f"can't edit while {self.name}"
class Draft(DocState):
name = "draft"
def edit(self, doc, text):
doc.text = text; return "edited"
def submit(self, doc):
if not doc.text: return "nothing to submit"
doc.state = Review(); return "sent for review"
class Review(DocState):
name = "in review"
def approve(self, doc):
doc.state = Published(); return "published"
def reject(self, doc):
doc.state = Draft(); return "back to draft"
class Published(DocState):
name = "published"
def reject(self, doc): return "already live; archive it instead"
class Document: # the context
def __init__(self):
self.state, self.text = Draft(), ""
def __getattr__(self, action): # forward submit/approve/reject/edit
return lambda *args: getattr(self.state, action)(self, *args)
doc = Document()
steps = [("submit",), ("edit", "Hello"), ("approve",), ("submit",), ("reject",),
("edit", "Hello, world"), ("submit",), ("approve",), ("edit", "oops")]
for action, *args in steps:
print(f"{action:8} -> {getattr(doc, action)(*args):32} [{doc.state.name}]")output
submit -> nothing to submit [draft]
edit -> edited [draft]
approve -> can't approve while draft [draft]
submit -> sent for review [in review]
reject -> back to draft [draft]
edit -> edited [draft]
submit -> sent for review [in review]
approve -> published [published]
edit -> can't edit while published [published]Run and edit it in the app.
Each rule sits in the state where it applies. Adding a Scheduled state is one new class plus the transitions into it; no other state's methods change.
What replaces the "if status == ..." ladders in the State pattern?
Watch it
Who decides the transition?
- States decide (above) — each state sets the next one. Rules are local; states know about each other.
- Context or a transition table decides — states only report events; a table maps
(state, event) → next state. Easier to see the whole machine at a glance; good when transitions change often.
When to use it — and when not to
Use it when an object's behaviour depends heavily on its state, the same status checks repeat across many methods, and states have distinct rules — orders, documents, connections, game characters, the vending machine. For two states and one method, an if is fine; for pure transition logic with little behaviour, a table-driven state machine is lighter than a class per state.
Pros and cons
| Pros | Cons |
|---|---|
| Each state's rules in one small class | More classes; overkill for few states |
| New states don't touch existing ones | States that set each other's successors are coupled |
| Illegal actions are handled explicitly per state | The full machine is spread across files |
How it relates
- Structurally the same as Strategy; the difference is that states replace themselves in response to events, while strategies are chosen from outside and rarely change themselves.
- State objects without fields can be shared (Flyweight) or single instances (Singleton).
- The refactoring Replace Type Code with State/Strategy (Organizing Data) leads here.
- The Vending machine and Elevator case studies are built around it.
State and Strategy have the same class diagram. What usually distinguishes them?
Key terms
- State pattern — An object delegates its behaviour to a state object that changes as its internal state changes.
- Context — The object whose behaviour varies; it holds the current state.
- Transition — Moving the context from one state to another in response to an event.
- Finite state machine — A model with a fixed set of states and rules for moving between them.