Skip to content

Hierarchical Machines

Hierarchical State Machines (HSMs) allow you to build nested state machines by defining child states within parent states. Matchina flattens these definitions into a single-level machine with dot-notation state keys.

Key benefits:

  • Modular definition - Define nested structure, run as flat machine
  • Simple runtime - No nested machine instances to manage
  • Type-safe transitions - Same send() API with compound states like "Working.Red"

Use hierarchy only when a state temporarily delegates control and must return it when finished. If states merely describe what is true, use a flat machine.

Good examples (temporary delegation):

  • Traffic light - Working contains Red/Green/Yellow cycle
  • Checkout flow - Payment contains MethodEntry/Authorizing/Authorized
  • Search bar - Active owns focus, contains TextEntry/Results

Bad examples (semantic grouping):

  • “Loading” nested under “Viewing” when both can exist independently
  • Using hierarchy as namespacing for unrelated concerns
import { createHSM } from "matchina/hsm";
export function createFlatTrafficLight() {
return createHSM({
initial: "Working",
states: {
Broken: {
on: {
repair: "Working",
maintenance: "Maintenance",
},
},
// Working is a hierarchical state with light cycle substates
Working: {
initial: "Red",
states: {
Red: {
on: {
tick: "Green",
},
},
Green: {
on: {
tick: "Yellow",
},
},
Yellow: {
on: {
tick: "Red",
},
},
},
// Parent-level transitions apply to all child states
on: {
break: "^Broken",
maintenance: "^Maintenance",
},
},
Maintenance: {
on: {
complete: "Working",
},
},
},
});
}
Section titled “Option 1: Declarative Hierarchy (Recommended)”

Use createHSM for clean hierarchical definition. This approach defines the hierarchy once and auto-resolves relative transitions. See the Hierarchical Traffic Light example for a complete working implementation.

const machine = createHSM({
initial: "Working",
states: {
Working: {
initial: "Red",
states: {
Red: { on: { tick: "Green" } },
Green: { on: { tick: "Yellow" } },
Yellow: { on: { tick: "Red" } },
},
on: { break: "Broken", maintenance: "Maintenance" }
},
Broken: { on: { repair: "Working" } },
Maintenance: { on: { complete: "Working" } }
}
});

Key concepts:

  • Define hierarchy naturally: Working contains Red, Green, Yellow
  • Parent transitions apply to all child states
  • Child transitions are specific to that state
  • Library handles flattening automatically

For cases where you need actual nested machine instances, use submachine and nestedHsmRoot. This approach creates separate machine instances that communicate via event propagation.

// Child machine factory
const createLightCycle = () => matchina(
lightStates,
{ Red: { tick: "Green" }, Green: { tick: "Yellow" }, Yellow: { tick: "Red" } },
"Red"
);
// Parent machine with embedded child
const controllerStates = defineStates({
Broken: undefined,
Working: submachine(createLightCycle),
Maintenance: undefined,
});
const createController = () => matchina(
controllerStates,
{ Broken: { repair: "Working" }, Working: { break: "Broken" }, Maintenance: { complete: "Working" } },
"Working"
);
// Enable hierarchical propagation
export function createPropagatingTrafficLight() {
const root = createController();
return nestedHsmRoot(root);
}

The library internally converts nested states to dot-notation keys:

  • Working with child Red"Working.Red"
  • Working with child Green"Working.Green"

Important: You define hierarchy naturally - the library handles the flattening internally. You never write "Working.Red" in your definitions.

  • Child transitions are automatically prefixed: Red: { tick: "Green" }"Working.Red": { tick: "Working.Green" }
  • Parent transitions apply to all child leaves: Working: { break: "Broken" } → both "Working.Red" and "Working.Green" get break: "Broken"
  • Initial states cascade: transitioning to "Working" goes to "Working.Red" (the child’s initial)

When parent and child define the same event, child wins (first-seen policy):

const machine = createHSM({
states: {
Working: {
states: {
Red: { on: { tick: "Green" } }, // Child handles "tick"
},
on: { tick: "Yellow" } // Parent also defines "tick"
}
}
});
// Child wins - "Working.Red" uses its own transition

See the Hierarchical Traffic Light example for concrete collision handling.

Key functions for hierarchical machines:

  • createHSM() - Clean hierarchical definition with automatic flattening
  • submachine() - Wrap machine factories for nested instances
  • nestedHsmRoot() - Enable event propagation between nested machines