Skip to content
FollowtheDamnCode

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

The document's lifecycle
The context delegates to the current state object

In code

A publishing workflow with one class per state
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

ProsCons
Each state's rules in one small classMore classes; overkill for few states
New states don't touch existing onesStates that set each other's successors are coupled
Illegal actions are handled explicitly per stateThe 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.