Skip to content

State Diagrams

State diagrams show how a machine transitions between states. They're ideal for technical documentation and understanding machine logic.

What State Diagrams Show

  • States as boxes or circles
  • Transitions as arrows between states
  • Events as labels on transitions
  • Guards as conditions on transitions

How States Are Derived

States in EventFlow are not arbitrary labels—they are derived from wait points in the lane diagram. Before diving into state diagrams, it's important to understand where states come from.

The Core Insight: STATE = WAIT POINT

A state exists only when the system is waiting:

  • Waiting for external input → Creates an intermediate state
  • Process complete (success/failure) → Creates a terminal state

This means you don't invent states during design sessions. Instead, you:

  1. Create lane diagrams first (Sessions 1-2 with full team)
  2. Identify wait points where the system pauses for external events
  3. Derive states from those wait points (Session 4, developer only)

States are defined explicitly in your flow file using the moves to #state syntax.

Basic Example

From this EventFlow:

flow
machine: @order

scenario: order lifecycle

  on :checkout from @customer (api)
    order moves to #awaiting_payment

  on :payment_success from @payment
    order moves to #paid

  on :payment_failed from @payment
    order moves to #payment_failed

  on :ship from @warehouse
    order moves to #shipped

Generates:

@order(start):checkout#awaiting_payment:payment_success:payment_failed#paid#payment_failed:ship#shipped─── = transition, green = final state

State Types

Initial State

The first state when a machine instance is created:

(initial):create#draft

Final States

States with no outgoing transitions:

#completed← success final#cancelled

Intermediate States

States with both incoming and outgoing transitions:

#pending← intermediate

Transitions with Guards

When transitions have conditions:

flow
on :approve from @manager
  ? amount < 1000
    order moves to #approved
  ? amount >= 1000
    order moves to #needs_director_approval
#pending[<1000][>=1000]#approved#needs_director_approval

Self-Transitions

When an event keeps the machine in the same state:

flow
on :retry from @system
  ? attempts < 3
    $attempts increases by 1
    // stays in #processing
#processing:retry[attempts < 3]

Complex State Machines

Order Lifecycle

@order#empty:add_item#cart:checkout#checkout:clear:submit#submitted:approve:reject#approved#rejected:ship#shipped:deliver#delivered

Branching and Merging

Branching

#pending:approve:reject:cancel

Merging

#approved#fast_track:process#completed

Reading State Diagrams

Follow the Flow

  1. Start at the initial state (○ or the first state)
  2. Follow arrows labeled with events
  3. Note guards in brackets [condition]
  4. End at final states (states with no outgoing arrows)

Identify Paths

  • Happy path: The expected successful flow
  • Error paths: Flows that lead to error/failure states
  • Recovery paths: Flows from error states back to normal flow

Use Cases

Technical Documentation

Include state diagrams in technical docs:

bash
eventflow diagram order.flow --type=state --format=svg

Code Reviews

Visualize changes to state machine logic:

bash
# Compare diagrams before/after changes
eventflow diagram order.flow --type=state > after.svg
git show HEAD~1:order.flow | eventflow diagram --type=state > before.svg

Debugging

Trace through states to understand behavior:

bash
eventflow diagram order.flow --type=state --highlight-path="checkout,payment_success,ship"

Best Practices

Derive, Don't Invent

States should be derived from wait points, not arbitrarily invented:

flow
// Good - corresponds to wait points
#awaiting_payment     // Waiting for :payment_success or :payment_failed
#pending_approval     // Waiting for :approve or :reject
#confirmed            // Terminal: process complete

// Avoid - doesn't correspond to waiting
#processing           // What is it waiting for?
#step_2               // Arbitrary label
#in_progress          // Too vague

When you find yourself creating a state, ask: "What external event is the system waiting for?" If there's no clear answer, the state may not be necessary.

States are defined explicitly using moves to #state syntax in your event handlers.

Keep States Focused

Each state should represent a clear, distinct condition:

flow
// Good - clear states
#draft
#pending_review
#approved
#rejected

// Avoid - vague states
#state1
#processing
#done

Name Transitions Clearly

Events on transitions should explain what triggers the change:

flow
// Good - clear transitions
:submit_for_review
:approve_order
:reject_with_reason

// Avoid - unclear
:next
:do
:ok

Document Terminal States

Make it clear which states are final:

#completed(final)← success#cancelled(final)

Released under the MIT License.