Definition

XState is a JavaScript and TypeScript library that implements finite state machines, hierarchical state machines, statecharts, and the actor model with explicit event-driven execution semantics.

The current production version is XState v5.

Lineage:

  1. Formal model: Harel statecharts (1987)
  2. Specification compatibility: SCXML
  3. Architectural model: actor system

This note covers XState as the implementation library. Two related notes cover adjacent concerns:

Note Lens Question answered
1.2.1 Formal model What is an FSM?
1.1.1 Distributed systems How is a state machine run safely?
1.2.9 Implementation How does XState provide both?

Core Model

XState v5 organises execution around four concepts: machine, actor, snapshot, and event. The formal definitions of state, event, transition, guard, and context live in 1.2.1; this section describes how XState v5 expresses them.

Machine

A machine is the declarative behavior definition: states, transitions, guards, actions, and invoked services.

A machine does not run by itself. It is interpreted by an actor.

import { createMachine } from 'xstate';

const machine = createMachine({
  id: 'order',
  initial: 'pending',
  states: {
    pending:   { on: { PAY: 'paid', CANCEL: 'cancelled' } },
    paid:      { type: 'final' },
    cancelled: { type: 'final' }
  }
});

Actor

An actor is a running instance of any logic that receives events and emits snapshots.

In XState v5, the actor is the fundamental unit of execution. A machine is one actor type. Other actor types include:

  1. promise actors (one-shot async)
  2. observable actors (streams)
  3. callback actors (custom event sources)
  4. transition actors (reducer functions)

Actor properties:

  1. mailbox
  2. internal state
  3. event processing
  4. snapshot emission
  5. parent-child communication
  6. lifecycle control (start, stop)

Snapshot

A snapshot is the observable state of an actor at a point in time.

{
  "value": "awaitingApproval",
  "context": { "refundAmount": 500 },
  "status": "active"
}

Snapshots are immutable. The actor produces a new snapshot on every transition.


Event

An event is a typed message sent to an actor.

{ type: 'APPROVED' }

Events trigger transitions. Events are immutable. Events must be declared in the typed event union.


Statechart Features Beyond FSM

XState's value over a hand-rolled FSM is the Harel statechart layer: hierarchy, parallelism, history, and delayed transitions. These cannot be expressed in a flat FSM (1.2.1) without state explosion.

Hierarchical states

Substates inherit transitions from their parent.

payment
├── validating
├── awaitingApproval
└── completed

A global event declared on the parent (e.g. CANCEL) fires from any active substate.

Used for:

  1. approval workflows
  2. nested agent execution
  3. multi-step enterprise processes

Parallel states

Multiple substates active simultaneously.

documentProcessing
├── extraction
└── complianceCheck

Both regions execute concurrently. Parallel regions are not part of classical FSM semantics; they are statechart-specific.


Delayed transitions

A transition that fires automatically after a time condition.

after: { 30000: 'timeout' }

Used for:

  1. approval expiration
  2. retry scheduling
  3. SLA enforcement

History states

A history state remembers the last active substate of a region. On re-entry, the machine resumes from the remembered substate rather than the initial state.

Used for:

  1. resume after pause
  2. multi-step wizards with back navigation

Mapping Formal Concepts to XState v5

The formal vocabulary in 1.2.1 maps to XState v5 syntax as follows.

Formal concept XState v5
State states.<name>
Event on: { EVENT_NAME: target }
Transition EVENT_NAME: 'targetState'
Guard guard: ({ context, event }) => boolean
Action actions: ['name'] or function references
Context context: { ... }
Terminal state type: 'final'
Invoked service invoke: { src: 'serviceName' }
Spawned actor spawn(...)
Hierarchy nested states
Parallelism type: 'parallel'
Delayed event after: { ms: target }
History type: 'history'

Type Safety

XState v5 favors typed actors with explicit event and context unions.

type Context = { refundAmount: number; approverId: string | null };

type Event =
  | { type: 'SIMPLE_CASE'; amount: number }
  | { type: 'REQUEST_APPROVAL'; amount: number }
  | { type: 'APPROVED'; approverId: string }
  | { type: 'REJECTED' };

Benefits:

  1. invalid transitions become compile errors
  2. event payloads are checked at the call site
  3. refactors propagate via the type system
  4. snapshot consumers receive narrowed types

This is the standard pattern for TypeScript production systems using XState v5.


Persistence

XState supports actor persistence via snapshot serialization. The actor's snapshot is rehydratable; the actor can be paused and resumed across process restarts.

const persisted = actor.getPersistedSnapshot();
const resumed   = createActor(machine, { snapshot: persisted });

For the orchestration concerns around persistence — when to persist, where to persist, idempotency, distributed coordination, durable approval gates — see 1.1.1 State machines in agentic systems.


Inspection

XState v5 ships with an inspection protocol and ecosystem tools.

  1. Stately Studio — visual graph editor and inspector
  2. XState Inspector — runtime tracing
  3. live state graphs
  4. transition tracing
  5. event log replay

Used for:

  1. debugging
  2. audit
  3. replay
  4. failure diagnosis
  5. documentation

The inspection protocol is library-defined and consumed by Stately Studio and third-party tooling.


XState vs Workflow Engines

XState and workflow engines (Temporal, Apache Airflow, AWS Step Functions, Google Cloud Workflows, Inngest, Trigger.dev) overlap in scope but differ in scale and assumptions.

Concern XState Workflow engine
In-process orchestration yes indirect
Cross-service orchestration manual native
Durable execution across infrastructure manual native
Heavy persistence guarantees manual native
Long-running workflows (hours, days, months) manual native
Multi-team workflow ownership weak native
Frontend ↔ backend coordination strong weak
Visual modeling and inspection strong varies
Type safety strong varies

Use XState for:

  1. application orchestration
  2. agent supervision inside one service
  3. approval flows
  4. UI ↔ backend state coordination
  5. bounded enterprise agent systems

Use a workflow engine for:

  1. cross-service orchestration
  2. infrastructure-scale durability
  3. multi-day workflows
  4. multi-team workflow ownership

The two are complementary. XState often runs inside a workflow engine task.


XState vs LangGraph

For the LLM-native comparison — graph-over-state vs state machine, durable execution, human-in-the-loop, and a combined-architecture recommendation — see 1.2.10 LangGraph.


XState in Agentic Systems

XState provides the deterministic orchestration layer around nondeterministic LLM operations. The machine owns transitions, approvals, retries, failure states, tool permissions, and audit checkpoints. The LLM proposes; the machine disposes.

For the full pattern — state snapshots exposed to the LLM, the event boundary that validates LLM output, tool authority, durable approval gates, and the architecture-selection table — see 1.1.1 State machines in agentic systems.


Worked Example

A typed XState v5 machine for an LLM-proposed refund workflow. This snippet demonstrates typed events, typed context, an invoked service, a delayed transition, and explicit terminal states.

import { createMachine, assign } from 'xstate';

type Context = { amount: number; approverId: string | null };

type Event =
  | { type: 'SIMPLE_CASE'; amount: number }
  | { type: 'REQUEST_APPROVAL'; amount: number }
  | { type: 'APPROVED'; approverId: string }
  | { type: 'REJECTED' }
  | { type: 'SUCCESS' }
  | { type: 'FAILURE' };

const refundMachine = createMachine({
  id: 'refund',
  initial: 'planning',
  types: {} as { context: Context; events: Event },
  context: { amount: 0, approverId: null },

  states: {
    planning: {
      on: {
        SIMPLE_CASE: {
          target: 'executeRefund',
          actions: assign({ amount: ({ event }) => event.amount })
        },
        REQUEST_APPROVAL: {
          target: 'awaitingApproval',
          actions: assign({ amount: ({ event }) => event.amount })
        }
      }
    },

    awaitingApproval: {
      after: { 86_400_000: 'failed' },
      on: {
        APPROVED: {
          target: 'executeRefund',
          actions: assign({ approverId: ({ event }) => event.approverId })
        },
        REJECTED: 'failed'
      }
    },

    executeRefund: {
      invoke: {
        src: 'callRefundAPI',
        onDone:  'completed',
        onError: 'failed'
      }
    },

    completed: { type: 'final' },
    failed:    { type: 'final' }
  }
});

Stacks

XState slots into the following common production stacks. The orchestration patterns themselves — when to use each, how state persists across restarts, how the LLM communicates with the machine — are covered in 1.1.1.

Node / TypeScript

  1. XState
  2. Fastify or NestJS
  3. Postgres
  4. Redis
  5. BullMQ
  6. OpenTelemetry
  7. Docker

Google Cloud

  1. Cloud Run
  2. Pub/Sub
  3. Firestore
  4. BigQuery
  5. Vertex AI
  6. Cloud Logging
  7. IAM

XState semantics do not change across stacks. Only the persistence and transport layers change.


Cross-references

  • 1.2.1 Finite State Machines (FSM) — the formal model XState implements.
  • 1.2.10 LangGraph — graph-over-state orchestration library; LLM-native counterpart for stateful agent workflows.
  • 1.1.1 State machines in agentic systems — orchestration patterns and distributed-systems concerns.
  • Hierarchical State Machines (this submodule) — the formalism that XState's statechart features extend.
  • Behavior Trees (this submodule) — alternative behavior formalism for reactive agents.

References

  1. XState — stately.ai/docs/xstate
  2. statelyai/xstate — github.com/statelyai/xstate
  3. Stately Studio — stately.ai
  4. Harel — Statecharts: A Visual Formalism for Complex Systems (1987)
  5. SCXML W3C Recommendation — w3.org/TR/scxml