UML State Diagrams

object lifecycle, events, and transitions, illustrated with TextFinder

1.  What a State Diagram Shows

A state diagram models the lifecycle of a single object or system: the distinct conditions it can be in, the events that drive it from one condition to another, and the actions it executes on entry, on exit, or as a transition fires. The diagram answers: what states does this object pass through, and what causes it to change? State diagrams are the right tool when an object's behavior depends on its history - when knowing the current inputs is not enough and you also need to know which state the object is currently in. A regex engine, a network connection, a parser, a UI widget, and a protocol handler all have this property. A pure function does not; drawing a state diagram for a stateless computation adds no information. Unlike an activity diagram, which shows the flow of control through an algorithm, a state diagram is tied to one object. Every transition is triggered by an event directed at that object. Every action executes on behalf of that object. This focus makes state diagrams precise about lifetime and about which operations are legal in which conditions - illegal operations are simply absent from the diagram.

2.  Notation

ElementMeaning
Initial pseudostate A filled circle with no incoming edges. Every state machine has exactly one. The transition leaving it carries no event label - the machine enters its first state on construction.
State A rounded rectangle. The top compartment holds the state name. Optional lower compartments list the internal behaviors: entry / action (executes on every entry), exit / action (executes on every exit), do / activity (executes continuously while in the state).
Transition An arrow from source state to target state, labeled with the full trigger syntax: event [guard] / action. All three parts are optional. A transition with no event label fires automatically when the source state's do-activity completes.
Event The named occurrence that can trigger the transition. A method call, a signal, a timeout, or a change condition. The event name appears before the guard.
Guard A boolean condition in square brackets: [queue empty], [match found]. The transition fires only when the event occurs and the guard is true. Two transitions from the same state on the same event must have mutually exclusive guards.
Action An operation that executes atomically when the transition fires, after the exit action of the source state and before the entry action of the target state. Written after the slash: find() / print path.
Composite state A state that contains nested states. A transition into a composite state enters its designated initial substate. A transition out of a composite state can fire from any substate. Composite states reduce diagram clutter by grouping related substates.
History pseudostate A circle containing H (shallow) or H* (deep) inside a composite state. When re-entering the composite state, the machine resumes the last active substate rather than always entering the initial substate.
Final state A bullseye - a filled circle inside a larger ring. Entering the final state signals that the object's lifecycle is complete. A composite state may have its own final state, signaling completion of that region only.

3.  Notation in Practice

The diagram below models a generic worker object - something any background task, thread pool slot, or async job runner could be. It shows initial and final states, transitions with events and guards, and an internal self-loop for progress updates.
stateDiagram-v2
    [*] --> Idle

    Idle --> Running : start(task)\n/ initialize resources
    Running --> Idle : cancel()\n/ release resources
    Running --> Running : progress()\n[not complete]
    Running --> Completed : progress()\n[complete] / emit result
    Running --> Failed : error(msg)\n/ log message
    Completed --> Idle : reset()
    Failed --> Idle : reset()

    Completed --> [*]
      

Figure 1. Worker state machine.

Two transitions leave Running on the same progress() event with mutually exclusive guards. The self-loop on Running fires repeatedly until the guard flips. reset() returns the object to Idle for reuse without reconstruction.

4.  TextFinder as a Worked Example

Within TextFinder the most stateful component is the traversal engine. DirNav transitions through a well-defined lifecycle: it is constructed idle, enters active traversal when search() is called, processes nested directories and files through a pair of composite substates, and returns to a post-search condition when all entries are exhausted. The Output component (TextFinder in the Rust variant) has a simpler but important lifecycle of its own: it must be configured with a compiled regex before find() is ever called. Calling find() in the Unconfigured state is a precondition violation. The state diagram makes that constraint explicit - no transition from Unconfigured leads to Scanning.
stateDiagram-v2
    direction TB

    [*] --> Startup

    state Startup {
        [*] --> ParsingArgs
        ParsingArgs --> ConfiguringOutput : parse() completes\n/ options extracted
        ConfiguringOutput --> ConfiguringDirNav : set_regex(pattern)\n/ regex compiled
        ConfiguringDirNav --> [*] : add_pattern(ext)
    }

    Startup --> Traversing : search(root, callback)

    state Traversing {
        [*] --> VisitingDirectory
        VisitingDirectory --> VisitingDirectory : [subdirectory found]\n/ fire do_dir
        VisitingDirectory --> MatchingFile : next file entry\n/ fire do_file
        MatchingFile --> VisitingDirectory : find() returns false
        MatchingFile --> PrintingMatch : find() returns true
        PrintingMatch --> VisitingDirectory : / print path
        VisitingDirectory --> [*] : [no more entries]
    }

    Traversing --> Complete : [all directories visited]
    Complete --> [*]
      

Figure 2. TextFinder state machine.

Two composite states: Startup is a linear initialization sequence; Traversing contains VisitingDirectory (which loops on itself for subdirectories) and transitions to MatchingFile for file entries. The match guard determines whether PrintingMatch fires.

5.  Reading the Diagram

Four observations follow from Figure 2. Startup is a one-way chute. The Startup composite state has no transitions back to itself and no transitions back from Traversing. Once parse() completes and components are configured, the application cannot re-enter startup. This is a design constraint, not just an implementation detail - the diagram makes it a first-class fact. The self-loop on VisitingDirectory models recursion. When DirNav encounters a subdirectory it fires do_dir and re-enters VisitingDirectory for the child. The state diagram does not model the call stack - that is a sequence diagram concern - but it does show that the directory-visiting behavior is re-entrant on the same state. MatchingFile has two exit transitions with guards. [find() returns false] and [find() returns true] are mutually exclusive and collectively exhaustive - no other outcome is possible. Any state with two outgoing transitions on the same implicit event must satisfy this property; the diagram enforces it by requiring the guards to cover all cases. Complete has no outgoing transitions to Idle. The application exits after one search. A variant that looped - waiting for a new query, running again, returning to Idle - would show a transition from Complete back to Startup or to a new Idle state. The absence of that edge documents the single-run design.

6.  Composite States and History

Composite states are the primary tool for managing complexity in state diagrams. Without them, a machine with ten substates and a cancel event would need ten explicit cancel transitions - one from each substate. With a composite state, a single transition from the composite captures them all. In Figure 2, any error or cancellation during traversal can be expressed as a single transition from the Traversing composite to a Cancelled state, rather than separate transitions from VisitingDirectory, MatchingFile, and PrintingMatch. The Traversing composite's exit action then handles resource cleanup regardless of which substate was active when the event fired. The history pseudostate (H or H*) extends this further. When a composite state is exited and later re-entered - for example, because a Paused event temporarily suspends traversal - the history pseudostate resumes the last active substate rather than restarting from the composite's initial pseudostate. TextFinder has no pause-and-resume feature, so history is not needed here, but it is essential in any stateful UI or interruptible protocol handler.

7.  State vs. Activity Diagrams

Both diagram types are behavioral, and both show transitions driven by conditions. The distinction is focus: state diagrams focus on a single object's lifecycle; activity diagrams focus on the flow of control through a process that may involve multiple participants.
Concern State diagram Activity diagram
Subject One object and its lifecycle. A process, workflow, or algorithm - often spanning multiple objects.
Transition trigger An event directed at the object: a method call, signal, or timeout. Completion of an action, or a guard becoming true.
Illegal operations Visible as absent transitions - if no edge leaves a state on event E, E is illegal in that state. Not expressible; activity diagrams show what happens, not what is forbidden.
Concurrency Concurrent regions inside a composite state - the object is in two substates simultaneously. Fork and join bars - two threads of action run in parallel.
TextFinder fit Models DirNav's traversal lifecycle and Output's configuration precondition. Models the search algorithm - nested loops and the regex match decision.
A useful test: if you find yourself writing "while in state X" or "only valid after Y has been called", you need a state diagram. If you find yourself writing "first do A, then B, unless C in which case D", you need an activity diagram.

8.  When to Draw One

Draw a state diagram when an object's valid operations depend on which state it is currently in. Specific triggers: Avoid state diagrams for objects that are purely computational with no lifecycle - a math utility class, a pure data holder, a stateless service. For those, a class diagram documents the interface; a state diagram would have only one state and add nothing.

9.  References

ResourceDescription
Project Story: TextFinder Architecture, CLI, performance, and code metrics for all five implementations.
UML Activity Diagrams The companion behavioral view showing the search algorithm as a flow of control rather than an object lifecycle.
UML Sequence Diagrams Shows the callback message protocol - the events that drive the state transitions modeled here.
Mermaid state diagram syntax Full syntax reference for Mermaid stateDiagram-v2, including composite states, concurrent regions, and notes.
UML State Machine Diagrams Overview Formal UML notation reference covering all pseudostates, regions, history, and entry/exit point semantics.