Background
Sections
IntroductionRequirements & Problem AnalysisConstraints & AssumptionsEstimation TechniquesFunctional vs Non-Functional RequirementsMoSCoW PrioritizationSystem Design FundamentalsArchitecture DiagramClass DiagramComponent DiagramData Flow Diagram (DFD)ER Diagram (Entity-Relationship Diagram)High Level Design (HLD)Low Level Design (LLD)Sequence DiagramState DiagramUse Case DiagramData StorageDocument StoresFile StorageGraph DatabasesIn-Memory DatabasesKey-Value StoresNewSQLNoSQL DatabasesObject StorageSQL Databases (RDBMS)Time-Series DatabasesWide-Column StoresDatabase ConceptsACID PropertiesCAP TheoremConsistency ModelsIndexingNormalization & DenormalizationReplicationSharding & PartitioningTransactions & Isolation LevelsScalabilityAuto-Scaling & ElasticityConsensus & Leader ElectionLoad BalancingReplication & Read ReplicasSharding & PartitioningVertical vs Horizontal ScalingAvailability & ReliabilityBackup & Data DurabilityCircuit BreakerData ConsistencyDisaster RecoveryFault Tolerance & FailoverGraceful DegradationHigh AvailabilityNetworkingCDNDNSFirewalls & VPNHTTP & HTTPSLoad Balancer & Reverse ProxyTCP/IP & UDPWebSocketsCachingCache InvalidationCache Read/Write PatternsCaching LayersEviction PoliciesRedis vs MemcachedMessaging & CommunicationDead-Letter QueueIdempotencyKafka vs RabbitMQ vs SQSMessage QueuesPub/SubCompute & ServicesAPI GatewayContainers & OrchestrationMonolith vs MicroservicesServerlessService DiscoveryService MeshWeb Server & App ServerAPI DesignAPI Versioning & IdempotencyAuthentication & AuthorizationGraphQLgRPCPaginationRate Limiting & ThrottlingRESTSecurityAuthentication & AuthorizationData PrivacyEncryptionInput Validation & InjectionOAuth2 & JWTSecrets ManagementXSS & CSRFStorage & File SystemsBackup & RetentionBlock vs File vs Object StorageData Lakes & WarehousesDistributed File SystemsEphemeral StorageObservability & MonitoringDistributed TracingHealth ChecksLoggingMetricsSLI, SLO, SLADesign PatternsBulkhead PatternCircuit Breaker PatternCreational PatternsRate Limiter PatternRetry PatternStructural & Behavioral Patterns

State Diagram

6 min read

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.

2D minimalistic state diagram showing five rounded rectangles connected by labeled arrows, with a filled circle as the start state and a bull's-eye as the end state, representing an order lifecycle

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

  1. They prevent illegal transitions — If there's no arrow from DELIVERED to PENDING, that transition is impossible. Without a diagram, developers might accidentally write code that allows it.

  2. They define the API — Each transition maps to an API endpoint or event handler. PENDING → PAID maps to POST /orders/{id}/pay. If there's no transition, there's no endpoint.

  3. 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.

  4. They drive database design — The state becomes a column (status ENUM(...)) and the transitions become the only valid UPDATE operations.

How to Build One

  1. List all states — Brainstorm every possible state the entity can be in
  2. Identify transitions — For each state, ask "what events can happen, and where do they lead?"
  3. Mark dead ends — States with no outgoing transitions are final states
  4. Check for orphans — States with no incoming transitions (except initial) are unreachable
  5. Add guards and actions — Under what conditions does a transition fire? What side effects does it trigger?

2D minimalistic diagram showing a state diagram being verified: one state highlighted in red has no incoming transitions (orphan), another has no outgoing transitions (dead end), with annotations explaining each issue

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

  1. 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.

  2. 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.

  3. 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.

  4. Q: What's a composite state, and when would you use one? Hint: A state that contains sub-states. Example: ACTIVE order might contain sub-states PROCESSING, IN_TRANSIT, OUT_FOR_DELIVERY. Composite states reduce diagram complexity — external transitions (like cancel) can target the composite state and apply to all sub-states, rather than drawing separate arrows from each.

  5. 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

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