State Diagram
In a Nutshell
A state diagram captures the lifecycle of an entity — all the states it can be in, the events that trigger transitions between states, and the transitions that are not allowed. An order goes from DRAFT → SUBMITTED → PAID → SHIPPED → DELIVERED. A payment goes from PENDING → AUTHORIZED → CAPTURED → REFUNDED. If you can't draw the state diagram for a core entity, you don't fully understand the domain — and your code will have bugs where illegal state transitions are silently allowed.

How It Actually Works
Notation
| Symbol | Meaning | Example |
|---|---|---|
| State | Rounded rectangle | PENDING, ACTIVE, SHIPPED |
| Transition | Arrow with label (event / guard / action) | paymentReceived → PAID |
| Initial state | Filled black circle | Where the lifecycle starts |
| Final state | Bull's-eye (circle inside circle) | Where the lifecycle ends |
| Guard condition | Bracket on transition [condition] |
[paymentValid] — transition only if true |
| Action | Slash after event event / action |
cancel / refundPayment — action triggered on transition |
| Composite state | State containing sub-states | ACTIVE containing IN_TRANSIT, OUT_FOR_DELIVERY |
Why State Diagrams Matter
They prevent illegal transitions — If there's no arrow from
DELIVEREDtoPENDING, that transition is impossible. Without a diagram, developers might accidentally write code that allows it.They define the API — Each transition maps to an API endpoint or event handler.
PENDING → PAIDmaps toPOST /orders/{id}/pay. If there's no transition, there's no endpoint.They reveal edge cases — What happens if payment fails? What if the user cancels after shipment? These scenarios are transitions in the diagram, and every missing arrow is a gap in your design.
They drive database design — The state becomes a column (
status ENUM(...)) and the transitions become the only validUPDATEoperations.
How to Build One
- List all states — Brainstorm every possible state the entity can be in
- Identify transitions — For each state, ask "what events can happen, and where do they lead?"
- Mark dead ends — States with no outgoing transitions are final states
- Check for orphans — States with no incoming transitions (except initial) are unreachable
- Add guards and actions — Under what conditions does a transition fire? What side effects does it trigger?

Seeing It in Action
Scenario: State diagram for an e-commerce order
● (start)
│
▼
┌─────────┐ addItem() ┌──────────┐ submitOrder() ┌───────────┐
│ DRAFT │──────────────▶│ DRAFT │──────────────────▶│ SUBMITTED │
│ (empty) │ │(has items)│ │ │
└─────────┘ └──────────┘ └─────┬─────┘
│ │
cancel() │ paymentReceived()
│ │
▼ ▼
┌──────────┐ ┌───────────┐
│CANCELLED │ │ PAID │
│ │ │ │
└──────────┘ └─────┬─────┘
▲ │
│ shipOrder()
cancel() │
[before ship] ▼
│ ┌───────────┐
├─────────────────────────│ SHIPPED │
│ │ │
│ └─────┬─────┘
│ │
│ confirmDelivery()
│ │
│ ▼
│ ┌───────────┐
│ │ DELIVERED │──▶ ◉ (end)
│ └─────┬─────┘
│ │
│ requestReturn()
│ [within 30 days]
│ │
│ ▼
│ ┌───────────┐
│ │ RETURNED │──▶ ◉ (end)
│ └───────────┘
Design decisions visible in the diagram:
- You can cancel a SUBMITTED or PAID order, but not a SHIPPED one — the guard
[before ship]prevents it - DELIVERED can transition to RETURNED only
[within 30 days]— a time-based guard - There's no transition from CANCELLED to anything — it's a terminal state
- DRAFT has a sub-state distinction: empty cart vs cart with items
Code implication:
class OrderStateMachine:
TRANSITIONS = {
'DRAFT': {'submit': 'SUBMITTED', 'cancel': 'CANCELLED'},
'SUBMITTED': {'pay': 'PAID', 'cancel': 'CANCELLED'},
'PAID': {'ship': 'SHIPPED', 'cancel': 'CANCELLED'},
'SHIPPED': {'deliver': 'DELIVERED'},
'DELIVERED': {'return': 'RETURNED'},
}
def transition(self, order, event):
current = order.status
if event not in self.TRANSITIONS.get(current, {}):
raise IllegalTransitionError(f"Cannot {event} from {current}")
order.status = self.TRANSITIONS[current][event]
Interview Questions
Q: Why is it important to model states explicitly rather than just using boolean flags? Hint: Boolean flags create invalid combinations. Two booleans (
is_paid,is_shipped) create 4 states, but only 3 are valid (you can't ship without paying). An explicit state enum prevents the impossible state and makes transitions auditable.Q: Design a state diagram for a payment transaction (authorization, capture, refund). Hint: States: PENDING → AUTHORIZED → CAPTURED → REFUNDED (or PARTIALLY_REFUNDED). Also handle: AUTHORIZATION_FAILED, CAPTURE_FAILED, VOIDED (cancel authorization before capture). Key insight: AUTHORIZED is a hold on funds, not actual movement — CAPTURE completes the transfer.
Q: How do state diagrams relate to event sourcing? Hint: In event sourcing, you store the events (transitions), not the current state. The current state is derived by replaying events through the state machine. The state diagram defines which event sequences are valid, and replaying produces the correct final state.
Q: What's a composite state, and when would you use one? Hint: A state that contains sub-states. Example:
ACTIVEorder might contain sub-statesPROCESSING,IN_TRANSIT,OUT_FOR_DELIVERY. Composite states reduce diagram complexity — external transitions (likecancel) can target the composite state and apply to all sub-states, rather than drawing separate arrows from each.Q: Your state diagram has 15 states and 40 transitions. How do you manage this complexity? Hint: Use composite states to group related states. Consider a hierarchical state machine (Statecharts). Split the diagram by concern — one for order lifecycle, one for payment lifecycle, with defined interaction points. In code, use a state machine library rather than hand-coding transitions.
References
- UML State Machine Diagram — Visual Paradigm — notation and examples
- Domain-Driven Design by Eric Evans — entity lifecycle patterns
- Statecharts — David Harel — the original paper on hierarchical state machines
Dive Deeper
- XState — JavaScript/TypeScript state machine library that visualizes state diagrams from code
- Practical UML Statecharts in C/C++ by Miro Samek — deep dive into implementing state machines in production
- State Pattern — Refactoring Guru — GoF State pattern for implementing state machines in OOP