skip to content

Why derive a device's session state machine from a declarative transition table instead of hand-writing the transitions?

level: seniorimportance: nice to knowfreq 32%

answer

  1. the graph becomes an artifact
  2. data can be walked, branches cannot
  3. unreachable, missing, ambiguous, terminal
  4. one table, code and diagram and tests
  5. guards escape into hooks

basics

~20 s

Because a transition table is data that can be checked before any code exists: unreachable states, missing or duplicated transitions and dead ends are found by walking the table. Hand-written transitions hide the same defects in control flow, and drift from the diagram everyone reasons about.

solid answer

~50 s

A declarative table of states, events and targets is an **external description**, so the generator can analyse it before emitting anything: it can walk the graph for states no event reaches, for a state-and-event pair with no transition, for two rules claiming the same pair, and for states nothing leaves. The emitted dispatch is then exhaustive by construction — every pair in the table has exactly one arm. Hand-written transitions bury the same structure in branches, where none of those checks is possible and where the code slowly disagrees with the diagram in the design document. The same table also feeds the diagram and the test matrix, so one artifact stays authoritative. The cost is expressiveness: guards and side effects have to live behind hooks the generated code calls, or the notation grows until it is a language of its own.

code

json · 11 lines
json
{
  "initial": "offline",
  "transitions": [
    { "from": "offline",     "on": "connect",   "to": "handshaking" },
    { "from": "handshaking", "on": "accepted",  "to": "streaming" },
    { "from": "handshaking", "on": "rejected",  "to": "offline" },
    { "from": "streaming",   "on": "overload",  "to": "throttled" },
    { "from": "throttled",   "on": "recovered", "to": "streaming" },
    { "from": "streaming",   "on": "close",     "to": "closing" }
  ]
}

go deeper

for a junior

Know that a state machine can be written down as a list of from-state, event and to-state rows, and that code can be produced from that list instead of written by hand.

for a middle

Explain what the list makes possible: walking it for unreachable states, missing pairs and duplicate rules before any code exists, and emitting dispatch that covers every declared pair.

for a senior

Show the whole trade: the checks you run before emission, the diagram and test matrix derived from the same table, and the guard problem that pushes conditions into hooks outside the checked graph.

for a principal

Judge when the notation is about to become a language. Each feature added to the table is one the generator must check, emit and document, and past some point a hand-written machine with good tests is the cheaper commitment.

## A table is something you can analyse; branches are not A device session moves through a small number of states — offline, handshaking, streaming, throttled, closing — driven by events. Written by hand, that machine is a set of branches: each one correct in isolation, collectively describing a graph nobody can see. Written as a **declarative transition table** and generated into source, the graph is the artifact and the branches are its output. The whole argument turns on what can be done to data that cannot be done to control flow. Before a single line is emitted, the generator can walk the table and answer questions a compiler never asks about hand-written branches. ## What the generator can check before it emits 1. **Reachability.** Walk from the initial state across the declared transitions, transitively. Any state not visited is dead code you are about to generate. 2. **Totality.** For every reachable state, is there a rule — or an explicit default — for every event that can arrive? A missing pair is the failure that in hand-written code appears as an event silently ignored. 3. **Determinism.** Two rules for the same state-and-event pair are ambiguous. In hand-written branches the first match wins and nobody notices the second. 4. **Liveness of terminals.** A state with no outgoing transition is either a genuine terminal or a session that can never close, and the table makes you declare which. These checks are cheap because the input is small, structured data. They are effectively impossible over branch code, which is why hand-written machines accumulate exactly these four defects. ## What else the one table feeds Generating from a description is not only about the code: - **The diagram** in the design document can be emitted from the same table, so it cannot drift from behaviour. - **The test matrix** — every state-and-event pair — can be enumerated from the table, turning "did we test the throttled path?" into a count rather than a memory. - **Operational reporting** — the set of legal states, and the events that cause each move, can be published for whoever reads the logs. | Concern | Hand-written transitions | Derived from the table | |---|---|---| | Unreachable state | Invisible | Reported before emission | | Missing state-and-event pair | Silent ignore at run time | Reported before emission | | Two rules for one pair | First match wins quietly | Rejected as ambiguous | | Diagram agrees with code | By discipline | By construction | | Adding a state | Edit every branch site | Add a row, regenerate | ## Where this stops paying It is a genuine trade, not a free win: - **Guards and actions escape the notation.** Real transitions are conditional — move to throttled only if the rate exceeded a threshold — and conditions are code. They belong behind a hook the generated dispatch calls, which means the table describes the graph and hand-written code supplies the predicates. The checks above still hold over the graph; they do not extend into the guards. - **The notation grows.** Once the table wants conditions, timers, entry and exit actions and hierarchical states, you are designing a language, and every feature you add is one the generator must check and emit. - **Debugging goes through emitted dispatch.** A step-through lands in generated code, which is unfamiliar until people have read it once. - **Small machines do not need it.** Three states and four transitions are clearer as branches than as a table plus a generator, and the honest senior answer says where the line is. ## The point to land in an interview The reason to derive the machine is not that generated code is better code. It is that the **table is checkable and the branches are not**, and that one checked artifact can be projected into code, a diagram and a test enumeration that cannot disagree with each other. Say that, name two of the four checks, and name the guard problem as the cost — that is the complete answer.

  • Transitions need conditions — throttle only above a rate threshold. Where does that live?
    Behind a hook: the table names a guard, the generated dispatch calls a hand-written implementation of it, and the graph checks continue to hold over the transitions. What you lose is analysis of the guards themselves, so keep them small and free of further branching, or the interesting behaviour moves out of the checked artifact entirely.
  • How small is too small for this treatment?
    When the table plus the generator is more machinery than the branches it replaces, and the graph is small enough to hold in your head. A handful of states with obvious transitions gains nothing. The value appears when states multiply, when several people edit the machine, or when a diagram and a test matrix must stay in step with it.
  • What does exhaustive dispatch buy over a default branch that ignores unknown events?
    It makes the ignoring explicit. With a table, a state-and-event pair either has a rule or is declared as deliberately ignored, and the generator reports pairs that are neither. A catch-all default in hand-written code cannot distinguish an event you decided to drop from one you forgot.

A timetable can be checked for a station no train stops at; the same journeys described as a hundred separate driver instructions cannot.

saying these in an interview costs you the question

  • Claims generated dispatch is faster and stops there
  • Thinks the table checks the guard conditions as well as the graph
  • Reaches for a table and a generator for a three-state machine
  • Keeps a separate hand-drawn diagram beside the table
  • Treats a missing state-and-event pair as harmless because the event is ignored