UML Activity Diagrams

control flow, decisions, and concurrency, illustrated with TextFinder

1.  What an Activity Diagram Shows

An activity diagram shows the flow of control through a process. Actions execute in sequence or in parallel; decisions fork the flow into alternative paths; loops return control to an earlier point. The diagram answers: what happens, in what order, under what conditions? Activity diagrams are the structured descendant of the flowchart. The notation adds two things a plain flowchart lacks: formal support for concurrency via fork and join bars, and swimlanes that partition actions by the responsible actor or component. Both additions make activity diagrams useful for documenting algorithms, workflows, and use-case realizations - not just simple sequential procedures. Unlike a sequence diagram, an activity diagram does not emphasize which participant sends which message. It emphasizes the logic of the flow itself. When branching and looping dominate the design problem, an activity diagram is the right choice; when the order of messages between specific objects matters more, use a sequence diagram instead.

2.  Notation

Activity diagrams use seven distinct elements. Mermaid renders them as flowcharts; the UML shape names are listed alongside the Mermaid equivalents.
UML Element Mermaid Shape Meaning
Initial node ((●)) filled circle The single entry point of the activity. Every diagram has exactly one.
Activity final node (((end))) bullseye circle Terminates the entire activity, canceling all concurrent flows.
Flow final node circle with X Terminates one concurrent flow without ending the activity.
Action node [action text] rounded rectangle A single step: a computation, a call, an I/O operation. Named as a verb phrase.
Decision / merge node {condition?} diamond Decision: one incoming flow, two or more outgoing flows with guard labels. Merge: two or more incoming flows, one outgoing flow. Same shape for both.
Fork / join bar thick horizontal bar Fork: one incoming flow splits into concurrent flows. Join: all concurrent flows must complete before the single outgoing flow continues. Mermaid approximates these as unlabeled nodes with multiple edges.
Swimlane subgraph partition A labeled column or row that groups all actions performed by one actor or component. Actions in the same swimlane share responsibility.
Guard labels on decision outflows are written in square brackets: [matched], [no match], [queue empty]. Every outgoing edge of a decision node must carry a guard, and the guards must be mutually exclusive and collectively exhaustive - otherwise the diagram is ambiguous.

3.  Notation in Practice

The diagram below shows the core elements in a minimal example: initial node, sequential actions, decision with guarded edges, a loop back, parallel fork and join, and activity final node.
flowchart TD
    I(( )) --> parse["parse input"]
    parse --> valid{"input valid?"}
    valid -->|"[yes]"| process["process item"]
    valid -->|"[no]"| err["report error"]
    err --> done(((end)))
    process --> more{"more items?"}
    more -->|"[yes]"| parse
    more -->|"[no]"| fork[ ]
    style fork fill:#333,stroke:#333
    fork --> t1["concurrent task A"]
    fork --> t2["concurrent task B"]
    t1 --> join[ ]
    t2 --> join
    style join fill:#333,stroke:#333
    join --> done
      

Figure 1. Core notation.

Sequential actions, guarded decision edges, a loop back to an earlier action, parallel fork and join, and activity final node. The solid bars approximate UML fork and join notation.

4.  TextFinder as a Worked Example

The TextFinder search algorithm is a natural activity diagram subject. It has a startup phase (argument parsing and component configuration), two nested iteration loops (directories and files), a conditional action (print only when the regex matches), and a clean termination. There is no concurrency in the baseline implementation - all traversal is single-threaded - so no fork or join bars appear.
flowchart TD
    I(( )) --> argv["parse argv via CommandLine"]
    argv --> cfgout["set regex on Output"]
    cfgout --> cfgdn["set extension pattern on DirNav"]
    cfgdn --> search["begin directory traversal"]
    search --> hasdir{"next directory?"}
    hasdir -->|"[yes]"| dodir["do_dir: record current directory"]
    dodir --> hasfile{"next file?"}
    hasfile -->|"[yes]"| dofile["do_file: build fully qualified path"]
    dofile --> apply["apply regex to file content"]
    apply --> hit{"regex match?"}
    hit -->|"[yes]"| print["print directory and filename"]
    print --> hasfile
    hit -->|"[no]"| hasfile
    hasfile -->|"[no]"| hasdir
    hasdir -->|"[no]"| done(((end)))
      

Figure 2. TextFinder activity diagram.

The outer loop iterates over directories; the inner loop iterates over files within each directory. The regex decision controls whether a path is printed. Both loops feed back to their respective decision nodes.

5.  Reading the Diagram

Four observations follow directly from Figure 2. Two nested loops, one conditional action. The outer loop is hasdir; the inner is hasfile. The conditional action (print) is inside the inner loop - it fires at most once per file. A reader can confirm the depth of nesting and the placement of the conditional without reading any code. The startup phase is linear. From the initial node to begin directory traversal, every action has exactly one incoming and one outgoing edge. There are no decisions and no loops in startup. That linearity is a design property, not an accident - it means startup always succeeds or throws immediately; it cannot partially configure and then stall. Guards are exhaustive. Every decision node has exactly two guarded edges whose conditions cover all cases: [yes] and [no], or [match] and [no match]. A diagram with an unguarded edge or a gap in guards signals an incomplete or ambiguous design. No error paths. The diagram shows the happy path only. TextFinder skips unreadable files silently - it does not branch to an error handler. That is a deliberate design choice. If error handling were added, it would appear as additional decision nodes after apply regex, with edges to error-reporting actions. The diagram makes the absence of error handling visible.

6.  Adding Swimlanes

Swimlanes partition the activity by the component responsible for each action. They answer: who does what? - a question the algorithm-only diagram defers. The TextFinder diagram partitions naturally into three swimlanes: EntryPoint (startup), DirNav (traversal decisions), and TfAppl / Output (callback actions and regex).
flowchart TD
    subgraph EP["EntryPoint"]
        argv["parse argv"]
        cfgout2["set regex on Output"]
        cfgdn2["set extension pattern on DirNav"]
    end
    subgraph DN["DirNav"]
        search2["begin traversal"]
        hasdir2{"next directory?"}
        hasfile2{"next file?"}
    end
    subgraph TF["TfAppl / Output"]
        dodir2["do_dir: record directory"]
        dofile2["do_file: build path"]
        apply2["apply regex"]
        hit2{"match?"}
        print2["print path"]
    end

    I2(( )) --> argv
    argv --> cfgout2
    cfgout2 --> cfgdn2
    cfgdn2 --> search2
    search2 --> hasdir2
    hasdir2 -->|"[yes]"| dodir2
    dodir2 --> hasfile2
    hasfile2 -->|"[yes]"| dofile2
    dofile2 --> apply2
    apply2 --> hit2
    hit2 -->|"[yes]"| print2
    print2 --> hasfile2
    hit2 -->|"[no]"| hasfile2
    hasfile2 -->|"[no]"| hasdir2
    hasdir2 -->|"[no]"| done2(((end)))
      

Figure 3. TextFinder activity diagram with swimlanes.

EntryPoint handles startup; DirNav owns the traversal decisions; TfAppl and Output own the callback actions and the regex match. Edges that cross swimlane boundaries represent the callback calls. The swimlane version makes the callback pattern explicit at a glance: every edge that crosses a swimlane boundary is a cross-component call. Edges within a swimlane are internal actions. A reader can verify the TextFinder design rule - no direct DirNav-to-Output call - by confirming that no edge connects the DN and TF swimlanes without passing through EP.

7.  Activity vs. Sequence Diagrams

Both activity and sequence diagrams are behavioral. The choice between them depends on what the diagram needs to communicate.
Concern Activity diagram Sequence diagram
Primary question What is the flow of control? Who sends what message, in what order?
Strength Decisions, loops, parallel branches, and algorithmic structure. Message ordering, return values, activation lifetimes.
Weakness Does not show which participant sends each message. Complex branching quickly becomes unreadable.
Best fit Algorithms, workflows, use-case realizations with multiple paths. Protocols, API call sequences, callback chains.
TextFinder fit Shows the search algorithm: nested loops, match decision, print action. Shows the callback protocol: DirNav fires do_dir and do_file on TfAppl.
For TextFinder both diagrams are useful and neither replaces the other. The activity diagram documents the search algorithm; the sequence diagram documents the interaction contract between DirNav and the callback receiver. Draw both when the project is complex enough to benefit from each perspective.

8.  When to Draw One

Draw an activity diagram when the question involves flow of control - what happens, in what order, under what conditions - rather than type structure or message participants. Specific triggers: Avoid activity diagrams for simple linear procedures - a numbered list is clearer and faster to read. Reserve them for flows where the branching, looping, or concurrency structure is the point of the communication.

9.  References

ResourceDescription
Project Story: TextFinder Architecture, CLI, performance, and code metrics for all five implementations.
UML Sequence Diagrams The companion behavioral view showing the callback message protocol rather than the algorithm flow.
UML Class Diagrams The structural view showing the types whose methods appear as action nodes in this diagram.
Mermaid flowchart syntax Full syntax reference for Mermaid flowchart, the notation used to render activity diagrams on this page.
UML Activity Diagrams Overview Formal UML notation reference covering all node types, edge notation, swimlanes, and exception regions.