The Problem It Solves
An order that can be cancelled after shipping is a production incident; so is an order stuck forever in “processing.” Both bugs come from implicit lifecycles — states and their legal transitions living only in scattered if (status == ...) checks. A state diagram makes the lifecycle explicit and complete: every legal path visible, every illegal transition declared impossible.
Anatomy
┌─────────┐ pay() [payment ok] ┌─────────┐
●─────────►│ PLACED ├──────────────────────►│ PAID │
(initial) └────┬────┘ └────┬────┘
│ cancel() │ ship()
▼ ▼
┌──────────┐ ┌──────────┐
│CANCELLED │ ┌─────│ SHIPPED │
└──────────┘ │ └────┬─────┘
▼ deliver()▼
┌──────────────┐
●──────────│ DELIVERED │───►◉ final
refund window └──────────────┘
notation: rounded box = state · arrow = transition labeled
event [guard] / action · ● initial · ◉ terminal
Each arrow carries the event that triggers it, optionally a guard ([payment ok] — transition only if true) and an action (/ send email). Anything without an arrow — “cancel a SHIPPED order” — is defined as impossible, which is precisely the value.
Mapping to Code
| UML element | Java form |
|---|---|
| States | enum OrderStatus |
| Transition table | Map<State, Map<Event, State>> or switch in one method |
| Guards | predicate evaluated before applying |
| Illegal event | exception or explicit rejection — never silent ignore |
enum Status { PLACED, PAID, SHIPPED, DELIVERED, CANCELLED }
class Order {
private Status status = Status.PLACED;
void pay(PaymentResult r) {
requireStatus(Status.PLACED); // guard by construction
if (!r.ok()) return; // guard condition
this.status = Status.PAID; // single mutation point
}
void cancel() {
if (status != Status.PLACED) throw new IllegalStateException(...);
status = Status.CANCELLED;
}
}
One mutation method per event, each validating current state first — invariants (“no cancel after ship”) become structural rather than disciplinary.
When to Graduate to the State Pattern
Two triggers: transitions carry state-specific behavior (each state defines different responses to the same events), or the switch table grows unwieldy. Then each state becomes a class delegating behavior — see behavioral/state page. For pure status tracking, the enum-plus-guards version above is simpler and correct.
Interview Application
- Drawing the order/booking/payment lifecycle before coding signals completeness thinking.
- The TTL question — “who fires the transition when a hold expires?” — expects either a scheduled job modeled as a time-based event (
after(15m) / expire) on the diagram.
Common Mistakes
- Terminal state missing → objects that can never be GC’d/archived.
- Guardless fan-in: allowing any state to reach CANCELLED — usually wrong; cancellation windows exist for reasons.
- Modeling screen flow instead of object lifecycle — states belong to domain entities.
Premium Content
Unlock State Diagrams and all premium lessons with a subscription.
From ₹199.99/year — See plans