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.

The usual escape, and why it is worse. Most codebases dodge the explosion by keeping one small enum and a handful of booleans next to it: 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.

Depth — a state may contain a whole machine. Anything true of the outer box is true of every box inside it, so a transition drawn once on the parent replaces n copies on the children. Attacks arrow duplication. Lesson 02.
Orthogonality — a state may be divided into regions that are all active at once. Two independent facts become two regions of 3 and 2 states, not one region of 6. Attacks box duplication. Lesson 03.
Broadcast communication — an event is delivered to the whole chart, and every region reacts to it or ignores it independently. That is what lets the regions stay ignorant of each other. Attacks coupling. Lesson 05.

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.

Configuration. The set of states that are active simultaneously. It always contains exactly one child of each active compound state, every child of each active parallel state, and all of their ancestors up to the root. SCXML §3.2; the SCXML algorithm calls it the configuration throughout.

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.

Status mapLesson 02 →

Sources: Harel 1987 · SCXML §3.2 · statecharts.dev · RESOURCES.md