Why this session exists
"The dashboard is slow" and "the job keeps retrying" are runtime stories, and Nova's zoned map from Session 2 cannot tell them - it has no concept of time or order. Sequence diagrams add the WHO-calls-whom-WHEN axis; state diagrams add the what-happens-to-one-thing-over-its-life axis. These two carry most incident writeups, design reviews, and debugging conversations you will ever have.
Sequence diagrams - conversations over time 8 min live
A sequence diagram is a transcript with geometry: participants across the top, time flowing DOWN, every arrow one message. If you can narrate "the dashboard asks the API, the API queries the warehouse...", you can already write one.
LiveParticipants, actors, and the arrow vocabulary4 min▶
Declare who is in the conversation first - participant draws a box, actor draws a stick figure for humans. The as keyword gives a short ID a long display name: participant api as Query API. Then every line is from ARROW to: message.
sequenceDiagram participant dash as Dashboard participant api as Query API dash->>api: solid arrowhead - a request api-->>dash: dotted arrowhead - the reply dash-)api: open arrow - async, no reply expected dash-xapi: cross - message that fails or is lost
| Arrow | Draws | Convention |
|---|---|---|
->> | solid line, filled arrowhead | a request or call |
-->> | dotted line, filled arrowhead | the reply or response |
-) | solid line, open arrowhead | async fire-and-forget (events, queues) |
-x | solid line, cross | a message that fails or is dropped |
-> | solid line, no arrowhead | rare - plain association |
participant dw as Warehouse - you will type dw thirty times). And declare participants in the order you want the columns - declaration order IS the layout.
LiveActivations, notes, autonumber - the Nova example4 min▶
Three finishing touches make a sequence read like documentation instead of scratchpaper. Append +/- to an arrow to activate/deactivate the receiver (the thin "busy bar" on the lifeline), Note over a,b: annotates, and autonumber numbers every message so people can say "step 4 is where it breaks".
sequenceDiagram autonumber actor ana as Analyst participant dash as Dashboard participant api as Query API participant dw as Warehouse ana->>dash: open revenue page dash->>+api: GET /metrics/revenue api->>+dw: SQL query Note over api,dw: reads the marts schema only dw-->>-api: result rows api-->>-dash: JSON payload dash-->>ana: rendered chart
- Activations stack:
->>+starts a busy bar on the receiver, the matching-->>-reply ends it. Nested calls nest the bars. - Note placement:
Note right of x:,Note left of x:, orNote over a,b:spanning two lifelines. - autonumber is one word on its own line, right after
sequenceDiagram. In an incident review, numbered steps are gold.
The latency hunt. A team argued for a week about why a dashboard took nine seconds. Someone finally drew the sequence: the API called the warehouse once per widget - twelve times per page load. The diagram made the N+1 pattern visible in thirty seconds. The fix was one batched query; the diagram went into the postmortem.
Control blocks - logic in the conversation 6 min live
Real conversations branch and repeat. Control blocks wrap a group of messages in a labeled frame: loop for repetition, alt/else for branching, opt for a maybe, par for simultaneous work, critical for must-happen sections, break for bailing out. All of them close with end.
LiveThe block vocabulary + Nova's retry logic4 min▶
| Block | Means | Shape |
|---|---|---|
loop label | these messages repeat | loop ... end |
alt label | mutually exclusive branches | alt ... else ... end |
opt label | happens or not - one optional branch | opt ... end |
par label | branches run at the same time | par ... and ... end |
critical label | must complete; options handle trouble | critical ... option ... end |
break label | flow stops here when this happens | break ... end |
Here is Nova's nightly CRM sync with its real retry logic - a loop around an alt, and a break for the give-up path:
sequenceDiagram
participant sch as Scheduler
participant ing as Ingestion
participant crm as CRM API
sch->>ing: run nightly sync
loop up to 3 attempts
ing->>crm: request export
alt export ready
crm-->>ing: file URL
else not ready yet
crm-->>ing: 202, retry later
end
end
break still failing after 3 attempts
ing-)sch: page the on-call
end
- Blocks nest freely - the alt lives inside the loop here. Two levels is readable; three is a smell.
- Labels are conditions: write them as facts a reader can check - "export ready", not "case 1".
Design reviews love alt blocks. "What happens when the export is NOT ready?" is the question senior engineers always ask. A sequence with an explicit else branch answers it before it is asked - and empty else branches in your draft are a to-do list of error handling you have not designed yet.
Self-studypar and critical - the concurrency blocks2 min read▶
Two blocks you will reach for less often but want on the shelf:
- par - work happening simultaneously:
par load dashboards...and refresh ML features...end. Nova's warehouse fan-out (dashboards and feature store both reading after a load) is a natural par. - critical - a section that MUST complete, with
optionbranches for what happens on trouble:critical acquire schema lock...option lock timeout...end. Perfect for migrations and exactly-once guarantees.
Also worth knowing from the docs: participants can carry clickable links via the link/links statements (menus on the participant box) - same spirit as flowchart click, same host-support caveats. We flag it as a docs lookup rather than core kit.
State diagrams - lifecycles 6 min live
A sequence diagram follows a conversation between many parties. A state diagram follows ONE thing through its life: a pipeline job, an order, a ticket, a model version. Nodes are states (nouns), arrows are transitions (events). If your team argues about "what does 'failed' actually mean", you need one of these.
LivestateDiagram-v2 - states, transitions, start and end3 min▶
Always use stateDiagram-v2 (the v2 renderer is the current one). [*] is both the entry point and the exit point - direction of the arrow decides which. Transitions take a label after a colon: the EVENT that causes the change.
stateDiagram-v2 direction LR [*] --> queued queued --> running : worker picks up running --> succeeded : all steps ok running --> retrying : transient error retrying --> running : backoff elapsed running --> failed : fatal error succeeded --> [*] failed --> [*]
- States are nouns (queued, running), labels are events (worker picks up). If a state name contains a verb phrase, it is probably a transition in disguise.
- Long names:
state "Waiting for upstream data" as waitgives a readable description to a short ID. - direction LR/TB works exactly like flowcharts - lifecycles usually read best LR.
- Choice points: declare
state verdict <<choice>>and fan transitions out of it with condition labels - one decision, many exits. You will see it in Demo 2.
LiveComposite states and notes2 min▶
A composite state is a state with an inner life: state running { ... } opens a block where the sub-states live, with their own [*] entry. From outside, "running" is one box; zoom in and it has extract, transform, load. This is the state-diagram version of a subgraph - and the cure for lifecycle diagrams that mix altitudes.
Notes attach context without adding states:
Self-studyConcurrency and the rest of the toolbox2 min read▶
- Concurrent regions: inside a composite state, a line with just
--splits it into regions that are active at the same time - a job that is simultaneously "processing" and "reporting heartbeats". Rare, but nothing else expresses it. - Fork and join:
state f <<fork>>andstate j <<join>>split one transition into parallel paths and merge them back - the transition-level cousin of concurrent regions. - Styling: stateDiagram-v2 supports classDef too, same spirit as Session 2 - one meaning per color still applies.
The status-field audit. A team drew the state machine for their pipeline's status column and found four states in the database that no code could ever reach, and one transition (failed to succeeded, no retry in between) that should have been impossible but happened weekly. The diagram was 15 lines; the cleanup ticket paid for the session.
The nightly sync, end to end ★ 8 min · follow along
We build Nova's full nightly CRM sync as one sequence: scheduler kicks it off, ingestion retries the flaky CRM API, quality checks rule on the batch, and the warehouse receives either data or a quarantine entry. Build it in mermaid.live layer by layer; the finished source is below.
sequenceDiagram
autonumber
participant sch as Scheduler
participant ing as Ingestion
participant crm as CRM API
participant qa as Quality checks
participant dw as Warehouse
sch->>+ing: run nightly sync
loop up to 3 attempts
ing->>crm: request export
alt export ready
crm-->>ing: file URL
else not ready yet
crm-->>ing: 202, retry later
end
end
ing->>+qa: validate rows
qa-->>-ing: verdict per batch
alt verdict is pass
ing->>dw: load to staging
else verdict is fail
ing->>dw: write to quarantine
end
Note over ing,dw: nothing is ever dropped silently
ing-->>-sch: run summary
Declare the five participants first, in column order: sch, ing, crm, qa, dw. Render - five lifelines, no messages, no errors.
Add the happy path only, straight through: kick off, one fetch, one validate, one load, one summary. Get the skeleton right before any logic.
Wrap the fetch in loop up to 3 attempts and split its outcome with alt export ready / else not ready yet. Watch the frames draw themselves.
Split the verdict: a second alt for pass vs fail, load vs quarantine. Notice you now have both alts a reviewer would ask about.
Add the polish layer: autonumber up top, activation +/- on ingestion and quality checks, and the Note over stating the no-silent-drops guarantee.
Read it back as an incident narrative: "at step 6 the CRM returned 202..." - if every step is citable, the diagram is review-ready. Save the share URL.
The job lifecycle, as a state machine ★ 7 min · follow along
Same nightly job, different lens: one run's life from queued to its final resting state. We add a choice point for the verdict, and a composite state that opens up "running" to show the steps inside.
stateDiagram-v2
[*] --> queued
queued --> running : worker picks up
state running {
[*] --> extract
extract --> transform
transform --> load
load --> [*]
}
running --> verdict : run finished
state verdict <<choice>>
verdict --> succeeded : checks pass
verdict --> quarantined : data checks fail
verdict --> retrying : transient error
retrying --> queued : backoff elapsed
retrying --> failed : attempts exhausted
succeeded --> [*]
quarantined --> [*]
failed --> [*]
note right of quarantined
bad rows parked for review,
never silently dropped
end note
Skeleton first: [*] --> queued --> running --> succeeded --> [*], each transition on its own labeled line. The happy path of one job.
Add the trouble states: retrying looping back to queued, failed and quarantined as terminal states with their own --> [*].
Insert the choice: state verdict <<choice>>, route running into it, and fan out three labeled exits. One decision point instead of three arrows tangled off "running".
Open the composite: wrap extract, transform, load inside state running { } with their own [*] entry. The outer diagram stays clean; the detail is there when you zoom.
Add the note on quarantined - the design guarantee this diagram exists to defend. Compare against Demo 1: same system, conversation lens vs lifecycle lens.
This exact diagram belongs in your pipeline's README next to the status enum. Every state in the diagram should exist in the code, and every state in the code should exist in the diagram - drift in either direction is a bug you can now see.
Sequence-ify a bug report ★ 4 min · build your own
The highest-value daily use of sequence diagrams: turning a prose incident description into a picture that everyone can point at. Here is your prose - go draw it.
List the participants named in the prose: scheduler, ingestion, CRM API, on-call (an actor), dashboard, and the analyst who saw stale numbers. Declare them in rough left-to-right order of involvement.
Convert each sentence into messages. "Asked three times, got not-ready every time" is a loop with a single outcome - no alt needed, that is the point of this incident.
"Gave up and paged" is a break block with an async -) message to on-call. "Kept serving yesterday's cache" is the dashboard replying to the analyst WITHOUT any upstream call - the silent failure, visible as an arrow that starts and ends downstream.
Add one Note over marking where an alert SHOULD have fired. Congratulations: the fix ticket writes itself from the diagram.
Keep your source - post it in the session channel. Next week we compare how differently people diagram the same prose, which is itself the lesson.
Try it yourself - this week ◐ 30-45 min total
- Sequence-ify one real flow you own end to end - an API you maintain, a report refresh, a deploy. Include at least one
altand one activation pair. Put it in the relevant README. - Draw the state machine for something with a status field in your database - a ticket, an order, a job. Then diff it against the actual enum in the code. Report what you find (there is always something).
- Rebuild Nova's nightly sync from memory, then diff against Demo 1. The blocks you forgot are the ones to drill.
- Take last quarter's gnarliest incident writeup and sequence-ify the first ten minutes of the timeline. Notice how many prose ambiguities ("the service failed" - which one? called by whom?) the diagram refuses to let you keep.
- Bring to Session 4: a table or two from a schema you know well - names and key columns. We draw entity relationships next, and Nova gets its data model.
Official docs covered
This session teaches the working content of the sequence diagram and state diagram pages at mermaid.js.org (mermaid 11.x). Left with the official docs: sequence box grouping, background highlighting, and the per-diagram config knobs.
Three questions before you go 🎯 ◐ 90 seconds
1 · The conventional arrow for a REPLY in a sequence diagram is...
Solid = request, dotted = reply. The dash count carries the meaning - readers decode call vs response before reading any label.
2 · Your flow has one optional step that either happens or does not - no second branch. Which block?
alt is for mutually exclusive BRANCHES; opt is the single maybe. An empty else is a design smell, not a syntax trick.
3 · When does a state earn composite treatment - state running { ... }?
Composites fix altitude mixing: job-level states outside, step-level states (extract, transform, load) tucked inside the phase they belong to.