1. What a Sequence Diagram Shows
A sequence diagram shows which participants exchange messages, in what order, and
what each message carries or returns. Time flows downward; participants appear as
vertical lifelines. An arrow crossing from one lifeline to another is a message -
a call, a signal, or a return value.
Sequence diagrams answer questions that structural diagrams cannot. A package
diagram shows that EntryPoint depends on DirNav; a sequence diagram shows that
EntryPoint calls search() once, after which DirNav drives all
subsequent interaction by firing callbacks. That distinction - who calls whom,
and who drives the loop - is invisible in any structural view.
One sequence diagram captures one scenario: a single path through the system for
a specific set of inputs. It does not try to show all possible paths simultaneously.
That constraint is a feature, not a limitation - it forces you to think clearly
about one scenario at a time, and to name it explicitly.
2. Notation
The core elements are participants, lifelines, messages, and activation boxes.
Combined fragments - loop, alt, opt,
par - extend the diagram to express repetition, branching, and
concurrency.
| Element | Meaning |
| Participant box |
A named actor, object, or component. Appears at the top; its lifeline
extends downward as a dashed vertical line.
|
Solid arrow ->> |
A synchronous message. The sender blocks until the callee returns.
The arrowhead points to the receiver.
|
Dashed arrow -->> |
A return message. Carries the response back to the caller. Often
labeled with the value or variable name returned.
|
Async arrow -)+ or -) |
An asynchronous message. The sender does not block - it continues
immediately after firing the message.
|
| Activation box |
A narrow rectangle on a lifeline marking the period during which that
participant is actively executing. Drawn with + / -
suffixes on message arrows in Mermaid.
|
loop [condition] |
A combined fragment enclosing messages that repeat. The condition
describes the iteration - "each file", "until queue empty".
|
alt [condition] |
A combined fragment with two or more branches separated by
else. Exactly one branch executes per occurrence.
|
opt [condition] |
A combined fragment whose body executes zero or one times,
depending on the condition.
|
par |
A combined fragment whose sections execute concurrently. Used when
threads, tasks, or coroutines run in parallel.
|
Note over A, B |
An annotation spanning one or more lifelines. Adds explanatory text
without adding a message.
|
3. Notation in Practice
The diagram below illustrates the core elements in isolation before applying them
to a real example.
sequenceDiagram
participant Caller
participant Callee
participant Worker
Caller->>+Callee: synchronous call (Caller blocks)
Callee-->>-Caller: return value
Caller-)Worker: async message (Caller does not block)
Note over Worker: executes independently
loop each item in collection
Caller->>Callee: process(item)
Callee-->>Caller: result
end
alt condition is true
Caller->>Callee: path A
else condition is false
Caller->>Callee: path B
end
Figure 1. Core notation.
Synchronous call with activation box, asynchronous fire-and-forget, loop fragment,
and alt fragment with two branches.
4. TextFinder as a Worked Example
TextFinder's runtime flow divides cleanly into two phases. In the first phase
EntryPoint parses options and configures the other components. In the second phase
EntryPoint calls search() and hands control to DirNav for the rest of
the run. DirNav drives every subsequent message; EntryPoint becomes a passive
callback host.
In the Rust implementation TfAppl - the type that implements the
DirEvent trait - acts as the callback receiver. DirNav calls
do_dir and do_file through the trait object;
TfAppl.do_file delegates the regex match to
TextFinder.
sequenceDiagram
participant EP as EntryPoint
participant CL as CommandLine
participant DN as DirNav
participant TF as TfAppl
participant OUT as TextFinder / Output
EP->>+CL: parse(argv)
CL-->>-EP: options (path, regex, ext, flags)
EP->>OUT: set_regex(pattern)
EP->>DN: add_pattern(ext)
EP->>+DN: search(root_path, &mut TfAppl)
loop each directory in tree
DN->>TF: do_dir(dir_name)
loop each file in directory
DN->>TF: do_file(file_name)
TF->>+OUT: find(fully_qualified_path)
OUT-->>-TF: matched: bool
alt matched == true
TF->>TF: print path to console
end
end
end
DN-->>-EP: search complete
Figure 2. TextFinder runtime sequence.
After search() is called, DirNav drives the entire traversal loop.
EntryPoint never sends another message - it waits for DirNav to return.
5. Reading the Diagram
Three observations stand out when reading Figure 2 as a design document.
Control inversion after search(). The activation box on DN's
lifeline spans the entire traversal. EntryPoint is active for the initial
configuration phase, then suspended while DirNav runs. This is the inversion of
control that the callback design produces: the library (DirNav) drives the
application (TfAppl), not the other way around.
TfAppl is the integration point. All callback messages land on TF.
It receives directory and file notifications from DN, delegates the match decision
to OUT, and writes to the console. Nothing in DN knows about OUT, and nothing in
OUT knows about DN. TfAppl exists precisely to connect them without creating a
direct dependency.
CmdLine is consulted only once. The activation on CL's lifeline
is short and early. After parse() returns, CL is not called again.
This tells a reader that CL is stateless after initialization - its data is
consumed at startup and does not need to persist across the traversal.
6. Combined Fragments in Depth
The two nested loop fragments in Figure 2 express that directory
traversal is a nested iteration: for each directory, DirNav visits each file.
The alt fragment inside the file loop expresses that output is
conditional - it fires only when the regex matches. These three fragments together
describe the complete runtime behavior of the traversal in a form that is more
precise than prose and more readable than code.
A common mistake is overloading a single sequence diagram with too many fragments.
When a diagram has more than two levels of nesting, consider splitting it: one
diagram for the startup phase, a second for the traversal loop with callbacks.
Label each diagram with a scenario name so a reader knows which path it covers.
The par fragment appears when the design uses threads or async tasks.
TextFinder is single-threaded, so it does not appear here. A multi-threaded variant
that scanned directories concurrently would add a par wrapping the
per-directory loop body, with one branch per worker thread.
7. What Sequence Diagrams Reveal
Structural diagrams show dependencies; sequence diagrams show consequences. The
same package diagram is consistent with designs that differ significantly at
runtime. A sequence diagram forces those differences into the open.
| Question | Only visible in a sequence diagram |
| Who calls whom first? |
The topmost message establishes the initiator. In TextFinder it is
always EntryPoint.
|
| Who drives the main loop? |
The participant with the longest activation box drives. Here it is DirNav,
not EntryPoint - a consequence of the callback design.
|
| Is the call synchronous or async? |
Solid vs. open arrowhead. A synchronous call blocks the caller's lifeline;
an async message does not.
|
| What is returned, and when? |
Dashed return arrows carry the value and show the moment control
transfers back. Structural diagrams only show that a dependency exists.
|
| Where does branching occur? |
alt fragments make conditional paths explicit with their
guard conditions labeled.
|
8. When to Draw One
Draw a sequence diagram when the question is about runtime interaction
between components, not about type structure or compilation boundaries. Specific
triggers:
-
Designing a callback or event-driven protocol: the diagram makes the inversion
of control explicit before any code is written.
-
Debugging a message-ordering bug: draw the intended sequence alongside the
observed sequence to locate the divergence precisely.
-
Reviewing an API: a sequence diagram of the expected call sequence tells library
users far more than a list of method signatures.
-
Documenting concurrency:
par fragments show which messages can
interleave and which must be ordered, giving a reviewer enough information to
reason about race conditions.
Keep each diagram focused on one scenario. A sequence diagram that tries to show
every possible path becomes a flowchart, which is better expressed as an activity
diagram. When branching dominates, reach for that diagram type instead.
9. References
| Resource | Description |
|
Project Story: TextFinder
|
Architecture, CLI, performance, and code metrics for all five implementations. |
|
UML Package Diagrams
|
Structural view showing the same TextFinder architecture at the compilation-boundary level. |
|
UML Class Diagrams
|
Structural view showing the types, members, and relationships that the sequence diagram orchestrates. |
|
Mermaid sequence diagram syntax
|
Full syntax reference including autonumber, actor aliases, background highlighting, and all fragment types. |
|
UML Sequence Diagrams Overview
|
Formal UML notation reference covering all message types, combined fragments, and interaction uses. |