Facebook Pixel

State Machine Diagrams

Many objects behave differently depending on what has already happened to them. A document that has been approved cannot be sent back to draft. An order that has shipped cannot be cancelled. When an object's valid operations depend on its current state, a state machine is the clearest model — and a state machine diagram makes that model visible at a glance.

A state machine diagram shows the states an object can be in (circles or rounded rectangles), the events that trigger movement between them (labeled arrows), and the initial state (a filled dot). Reading it tells you every legal transition and, by omission, every illegal one.

A document review workflow

Consider a content-management system where documents move through an editorial pipeline before publishing. A document starts as a draft. An author submits it for review, moving it to in_review. A reviewer either approves it (moving to approved) or rejects it (sending it back to draft for revision). An approved document can be published; a published document can be archived.


Every arrow is a named event. The absence of an arrow is a constraint: there is no draft --> published transition, which means code cannot skip the review step without going through the diagram's defined path. Drawing the diagram before writing any code makes the allowed transitions explicit and provides a reference for the implementation.

Translating the diagram to code

The most direct translation is an enum for the states and a method for each event. Each method checks the current state, raises an error if the transition is invalid, and updates the state if it is allowed. This is the tell, don't ask principle applied to state: callers tell the document to advance; they do not read its state and branch on it themselves.

1from enum import Enum, auto
2
3class DocumentState(Enum):
4    DRAFT     = auto()
5    IN_REVIEW = auto()
6    APPROVED  = auto()
7    PUBLISHED = auto()
8    ARCHIVED  = auto()
9
10class Document:
11    def __init__(self, title: str):
12        self._title = title
13        self._state = DocumentState.DRAFT
14
15    @property
16    def state(self) -> DocumentState:
17        return self._state
18
19    def submit(self) -> None:
20        self._require(DocumentState.DRAFT)
21        self._state = DocumentState.IN_REVIEW
22
23    def approve(self) -> None:
24        self._require(DocumentState.IN_REVIEW)
25        self._state = DocumentState.APPROVED
26
27    def reject(self) -> None:
28        self._require(DocumentState.IN_REVIEW)
29        self._state = DocumentState.DRAFT
30
31    def publish(self) -> None:
32        self._require(DocumentState.APPROVED)
33        self._state = DocumentState.PUBLISHED
34
35    def archive(self) -> None:
36        self._require(DocumentState.PUBLISHED)
37        self._state = DocumentState.ARCHIVED
38
39    def revoke(self) -> None:
40        self._require(DocumentState.APPROVED)
41        self._state = DocumentState.DRAFT
42
43    def _require(self, expected: DocumentState) -> None:
44        if self._state is not expected:
45            raise ValueError(
46                f"cannot perform this action in state {self._state.name}"
47            )
Invest in Yourself
Your new job is waiting. 83% of people that complete the program get a job offer. Unlock unlimited access to all content and features.
Go Pro