Design Patterns (GoF and modern) Module 5 – Behavioral patterns: state and communication
State: behaviour that changes with state
State lets an object change its behaviour when its internal state changes. A vending machine as state classes, an enum with match and a transition table.
What you will learn
- Implement a state machine with state objects, then with enums and pattern matching
- Decide where transitions live, in the context, in the state objects or in a table
- Test every edge of the state diagram, illegal transitions included
- Recognise when a flat state machine needs hierarchical states (statecharts)
Before you start
What you’ll learn · Before you start
What you will learn
- Implement a state machine with state objects, then with enums and pattern matching
- Decide where transitions live, in the context, in the state objects or in a table
- Test every edge of the state diagram, illegal transitions included
- Recognise when a flat state machine needs hierarchical states (statecharts)
Before you start
On this page
State is a behavioural design pattern for an object whose behaviour depends on the situation it is in. Instead of checking a status flag in every method, the object keeps a current state, and each state decides what every event does: which events it accepts, which it refuses and which state comes next. When an event moves the object to a new state, its behaviour changes with it, so from the outside the object seems to change its class. Vending machines, order lifecycles, network connections, document workflows and the modes of an editor are all state machines.
How State works
A vending machine shows the problem. It can be idle, holding money, dispensing or sold out, and the same coin means
different things in each: it starts a purchase when idle and must be refused while an item is being dispensed. Code
that stores a status string and checks it with if statements in every method spreads each state across all the
methods, so adding a state means editing every one of them, and a forgotten check silently accepts an event that
should have been refused.
The State pattern puts each state’s behaviour in one place and makes every transition explicit. The machine’s states and transitions, drawn straight from its transition table:
The vending machine, drawn from its transition table
Text description of the diagram
Four states are drawn as boxes: idle, has money, dispensing and sold out. Arrows between them are the transitions, each labelled with the event that causes it.
- From idle, "coin" leads to has money, and "restock" loops back to idle.
- From has money, "refund" leads back to idle, and "choose" leads to dispensing.
- From dispensing, "done" leads to idle when items are left, and to sold out when none are left. The two conditions in square brackets are guards.
- From sold out, "restock" leads back to idle.
Any event not drawn from a state, such as "coin" while dispensing, is refused in that state. The diagram's source is generated from the same transition table the code uses.
The shortest correct implementation is that table, as data. The machine looks up its current state and the event; an entry gives the next state, and a missing entry means the event is refused in that state:
"""State as a transition table: the machine's current state decides what each event does, and an event the table
has no entry for is refused."""
# (state, event) -> next state, or a function of the machine when the next state depends on its data (a guard)
TRANSITIONS = {
("idle", "coin"): "has money",
("idle", "restock"): "idle",
("has money", "refund"): "idle",
("has money", "choose"): "dispensing",
("dispensing", "done"): lambda machine: "idle" if machine.stock else "sold out",
("sold out", "restock"): "idle",
}
class VendingMachine:
def __init__(self, stock):
self.state = "idle"
self.stock = stock
def handle(self, event):
target = TRANSITIONS.get((self.state, event))
if target is None:
return f"{event:8} refused in {self.state}"
if event == "choose":
self.stock -= 1
elif event == "restock":
self.stock += 3
before, self.state = self.state, target(self) if callable(target) else target
return f"{event:8} {before} -> {self.state} (stock {self.stock})"
machine = VendingMachine(stock=1)
for event in ["choose", "coin", "choose", "coin", "done", "coin", "restock", "coin", "refund"]:
print(machine.handle(event)) Output
choose refused in idle coin idle -> has money (stock 1) choose has money -> dispensing (stock 0) coin refused in dispensing done dispensing -> sold out (stock 0) coin refused in sold out restock sold out -> idle (stock 3) coin idle -> has money (stock 3) refund has money -> idle (stock 3)
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 vending.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The first “choose” is refused because nothing has been paid. After the only item is sold, “done” leads to sold out instead of idle: that entry is a guard, a condition on the machine’s data that decides between two next states. In sold out every coin is refused until a restock.
State machines are written in a few standard ways, and each has a name:
- State classes give each state its own class with a method per event, the form in the Gang of Four catalogue.
- An enum with match keeps the states as values and handles every state and event in one function.
- A transition table lists each state and event with its next state, as data that is easy to draw and test.
- Typestate gives each state its own type, so that an illegal transition does not even compile.
- Statecharts add nested and parallel states to machines whose flat diagrams would explode.
Three implementations, tested by one matrix Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Where the transitions live
- Testing every edge, including the refused ones
- A diagram drawn from the table
- The same machine in six languages
- When a flat machine needs a statechart
- State or Strategy?
- Exercise: an order lifecycle
- An order lifecycle with guarded transitions and repeat-safe events
- Check yourself
- Interview questions
- How is the State pattern different from the Strategy pattern?
- An order's status is a string column, and code all over the service checks it with if statements before changing it. About twice a week an order is shipped after it was cancelled. How do you find the cause, and how do you redesign the lifecycle so that it cannot happen again?
- A payment flow is a state machine with 12 states and 9 events. How do you test it so that every legal and illegal transition is covered, duplicate events are safe, and the diagram in the documentation can never disagree with the code?
- A media player's state machine has stopped, playing and paused. Product wants a lockable screen, a mute switch and an offline mode, each independent of the others and of playback, and a "power off" that works from anywhere. Count what a flat machine needs, and redesign it.
- Key takeaways
5 runnable examples (C++, Go, Java, Python, Rust, TypeScript, languages_session.py, session.py) · 1 exercise with sample tests · 3 interview answers · about 21 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
References
- Design Patterns: Elements of Reusable Object-Oriented Software (State) (Addison-Wesley (Pearson))
- The Rust Programming Language: Implementing an Object-Oriented Design Pattern (The Rust Project)
- Statecharts: a visual formalism for complex systems (Science of Computer Programming (David Harel))
- State Chart XML (SCXML): State Machine Notation for Control Abstraction (W3C)
- The Java Language Specification, Java SE 25: enum classes (§8.9) (Oracle)
- The match statement (Python 3.14 language reference) (Python Software Foundation)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress