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:
- Formal model: Harel statecharts (1987)
- Specification compatibility: SCXML
- 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:
- promise actors (one-shot async)
- observable actors (streams)
- callback actors (custom event sources)
- transition actors (reducer functions)
Actor properties:
- mailbox
- internal state
- event processing
- snapshot emission
- parent-child communication
- 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:
- approval workflows
- nested agent execution
- 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:
- approval expiration
- retry scheduling
- 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:
- resume after pause
- 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:
- invalid transitions become compile errors
- event payloads are checked at the call site
- refactors propagate via the type system
- 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.
- Stately Studio — visual graph editor and inspector
- XState Inspector — runtime tracing
- live state graphs
- transition tracing
- event log replay
Used for:
- debugging
- audit
- replay
- failure diagnosis
- 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:
- application orchestration
- agent supervision inside one service
- approval flows
- UI ↔ backend state coordination
- bounded enterprise agent systems
Use a workflow engine for:
- cross-service orchestration
- infrastructure-scale durability
- multi-day workflows
- 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
- XState
- Fastify or NestJS
- Postgres
- Redis
- BullMQ
- OpenTelemetry
- Docker
Google Cloud
- Cloud Run
- Pub/Sub
- Firestore
- BigQuery
- Vertex AI
- Cloud Logging
- 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
- XState —
stately.ai/docs/xstate - statelyai/xstate —
github.com/statelyai/xstate - Stately Studio —
stately.ai - Harel — Statecharts: A Visual Formalism for Complex Systems (1987)
- SCXML W3C Recommendation —
w3.org/TR/scxml