UML State Machine Diagrams: A Complete Guide
State machine diagrams model how an object moves between states in response to events. This guide covers states, transitions, guards, entry and exit actions, and nested composite states.
Some of the nastiest bugs in software come from stateful objects behaving in ways nobody anticipated: an order that got shipped after it was cancelled, a connection that tried to send data while it was closing, a subscription stuck in a status it should never have reached. A state machine diagram is the antidote. It enumerates every state an object can be in, every event that moves it between states, and, just as importantly, which transitions are impossible. Drawing one before you write the code often surfaces the exact edge cases that would otherwise become production incidents.
This guide covers the notation in the order you need it: states and the initial and final pseudostates, transitions and the events that trigger them, guards and actions, and composite states for managing complexity. You can draw these at /diagrams, and they pair naturally with the sequence diagrams described at /diagram-tools/sequence-diagram when you want both the states and the interactions.
States and pseudostates
A state is a condition an object is in for some span of time, drawn as a rounded rectangle labeled with the state's name: Draft, Submitted, Approved, Shipped. The object is always in exactly one of these states at a time, which is the property that makes the model so useful for reasoning. The initial pseudostate, a small solid circle, marks where an object begins, with an arrow to its first real state. A final state, a circle inside a ring, marks the end of the object's lifecycle if it has one.
States can carry internal activity. Inside the state box you can list an entry action (performed every time the state is entered), an exit action (performed on every departure), and a do activity (ongoing work while in the state). For example, an "Uploading" state might have an entry action of "start transfer" and a do activity of "update progress bar". This keeps behavior that belongs to a state attached to that state rather than scattered across transitions.
Transitions, events, and guards
Transitions are arrows between states, and they are labeled with the full syntax "event [guard] / action". The event is what triggers the transition, such as "submit" or "paymentReceived". The guard, in square brackets, is a boolean condition that must be true for the transition to fire, like [balance >= amount]. The action after the slash is behavior executed as the transition happens. All three parts are optional except that a transition needs something to make it meaningful.
Guards are the feature that lets a single event lead to different states depending on conditions. An "Order" receiving a "checkout" event might transition to "Confirmed" if [inStock] is true and to "Backordered" otherwise, using two transitions on the same event with complementary guards. This is far clearer than burying the branching logic in code, because the diagram shows at a glance that checkout has two possible outcomes and exactly what distinguishes them.
Composite states: taming complexity
Real objects can have dozens of states, and a flat diagram becomes a tangle. Composite states solve this by nesting a whole state machine inside a single state. A top-level "Active" state might contain the substates "Idle", "Processing", and "Waiting", with their own transitions among them. A transition out of the "Active" boundary applies to all its substates at once, so you can say "any active state, on 'shutdown', goes to Terminated" with a single arrow.
Composite states also support the concept of history, drawn as a circled H, which remembers which substate was active when the composite was last exited so it can return there. This is exactly what you want for something like a media player that resumes where it left off. Used well, nesting turns an unreadable forty-state sprawl into a clean hierarchy you can zoom in and out of mentally.
A worked example and practical tips
Consider a support ticket. Its states are New, Open, Pending, Resolved, and Closed. From New, an "assign" event moves it to Open. From Open, a "requestInfo" event moves it to Pending, and a "resolve" event moves it to Resolved. From Pending, "customerReplied" returns it to Open. From Resolved, a "reopen" event with a guard [within 30 days] returns it to Open, while a "close" event moves it to the final Closed state. Laid out, this diagram makes the whole lifecycle and its rules obvious, including that you cannot go straight from New to Closed.
- List states as nouns or adjectives describing a condition, not as actions.
- Label every transition with its triggering event so the diagram is unambiguous.
- Use guards to handle a single event that can lead to different outcomes.
- Attach recurring behavior to states as entry, exit, or do activities.
- Use composite states to group related substates once you pass roughly seven states.
- Explicitly ask which transitions are impossible; the absent arrows are as important as the present ones.
- Keep one diagram to one object's lifecycle rather than mixing several.