Statecharts · Lesson 01
The Configuration, Not the State
Why flat machines double every time somebody adds a feature, the three things Harel added to fix it, and the one word you have to stop using.
The machine that doubles
A flat state machine for a media player: stopped, playing,
paused. Three boxes, six arrows, nothing to argue about.
Then the product manager asks for mute. Mute is not one of the three — you can be muted while playing, muted while paused, muted while stopped. So the three boxes become six:
graph LR A[stopped
sound on] <--> B[playing
sound on] B <--> C[paused
sound on] A <--> C D[stopped
muted] <--> E[playing
muted] E <--> F[paused
muted] D <--> F A <-. mute .-> D B <-. mute .-> E C <-. mute .-> F
Then subtitles on or off. Twelve. Then a playback speed with three settings. Thirty-six. Each new independent fact multiplies the diagram, and every arrow you already drew has to be redrawn in each copy. This is state explosion, and it is the reason flat machines have a reputation for not scaling past a whiteboard.
status,
isMuted, hasSubtitles, speed. That is the same 36 states,
but now the machine has no idea which of the 36 are legal. status = "stopped" with
speed = 2 is representable, reachable, and meaningless. You have traded a diagram that
is too big to read for a state space that cannot be checked at all.
Harel's three additions
Harel's 1987 paper opens with the formula, and it is worth memorising in his words: statecharts = state-diagrams + depth + orthogonality + broadcast communication. Three additions to a notation everybody already knew. Each one attacks the explosion from a different side.
The 36-state player, rewritten with two of the three:
stateDiagram-v2
state session {
state transport {
[*] --> stopped
stopped --> playing : play
playing --> paused : pause
paused --> playing : play
playing --> stopped : stop
paused --> stopped : stop
}
--
state audio {
[*] --> sound_on
sound_on --> muted : mute
muted --> sound_on : unmute
}
}
Five boxes for what took six, and adding subtitles costs a third region of two boxes
rather than a doubling. More importantly: muted now has exactly one arrow in and one out,
wherever the transport happens to be.
The word that changes: configuration
Once states can contain states and regions can run in parallel, “the current
state” stops being a well-formed question. The machine above is, right now, in
session and transport and stopped and
audio and sound_on — five states at once.
This is not pedantry, it is the API. In python-statemachine:
>>> sm = Player()
>>> {s.id for s in sm.configuration}
{'session', 'transport', 'stopped', 'audio', 'sound_on'}
>>> sm.send("mute")
>>> {s.id for s in sm.configuration}
{'session', 'transport', 'stopped', 'audio', 'muted'}
The leaves are what you usually care about; the ancestors are what make questions like “is this connection being torn down?” answerable without a hand-maintained list of state names. You will use both.
Exercise 1 — name the construct
For each complaint, which of Harel's three additions is the direct answer? Answer from the shape of the complaint, not from the domain.
Exercise 2 — count the configuration
Given this chart, how many states are in the configuration at the moment described? Count ancestors. The root is not a state here — start at the outermost box drawn.
stateDiagram-v2
state alive {
state main {
[*] --> displays
state displays {
[*] --> time
time --> date : d
}
displays --> beeping : alarm
}
--
state light {
[*] --> light_off
light_off --> light_on : b
}
--
state power {
[*] --> ok
ok --> weak : drain
}
}
Exercise 3 — recall
Where this goes
Three constructs, three lessons. Depth first, because it is the one that changes how you read an existing diagram: once boxes can nest, the question “which transition fires?” has a non-obvious answer, and getting it wrong is the most common way a hierarchical machine misbehaves.
Sources: Harel 1987 · SCXML §3.2 · statecharts.dev · RESOURCES.md