yay-machine borrows the best ideas from other JS/TypeScript state-machine and state-management libraries.
It will feel familiar to XState users, with features including:
JSON config
state-data (context / extended-state / dynamic state)
conditional transitions (guards)
immediate (always) transitions
event handling in specific-state or any-state
similar creation and lifecycle API
And yay-machine brings new ideas of its own:
states are types (think: interface). They are an object with a name and any associated data. Different states may have different associated data
side-effects are all you need for sync/async interactions with the current machine and outside world
We prefer contemporary accessible language, to traditional academic terms.
yay-machine has only a handful of concepts and the API is minimal
states
events
transitions
side-effects
These features can be combined to model just about anything.
It should be quick to learn, and have a minimal ongoing cost-of-ownership.
yay-machine is a tiny package and won’t bloat your app bundles.
Minimal implementation means minimal overhead and you can trust yay-machine with your high-performance workloads.
yay-machine vs XState: all benchmarks
UI: sign-up form, form session (38 events): yay-machine 1.30M events/s, XState 162k events/s; 8.1× faster. Event stream: long-lived connection, replay 1,000-event trace: yay-machine 5.24M events/s, XState 283k events/s; 19× faster. Fleet: 1,000 live connections, 1 event to each of 1,000 instances: yay-machine 2.18M events/s, XState 192k events/s; 11× faster. Login request, login success + logout: yay-machine 271k requests/s, XState 18.9k requests/s; 14× faster. Login request, login rejected + retry: yay-machine 320k requests/s, XState 18.0k requests/s; 18× faster. Memory per live instance, started, idle (disconnected): yay-machine 1.23 KiB per instance, XState 2.69 KiB per instance; 2.2× less memory. Memory per live instance, connected (with heartbeat resource): yay-machine 1.33 KiB per instance, XState 3.94 KiB per instance; 3.0× less memory. Toggle, send TOGGLE: yay-machine 3.53M events/s, XState 164k events/s; 22× faster. Counter, INCREMENT + DECREMENT (guards pass): yay-machine 1.35M events/s, XState 254k events/s; 5.3× faster. Counter, INCREMENT beyond max (guard rejects): yay-machine 9.29M events/s, XState 828k events/s; 11× faster. Guards, 1st guard matches: yay-machine 2.08M events/s, XState 268k events/s; 7.8× faster. Guards, 3rd guard matches: yay-machine 1.79M events/s, XState 287k events/s; 6.3× faster. Guards, default (5th) matches: yay-machine 1.93M events/s, XState 236k events/s; 8.2× faster. Immediate transitions, routes via 1st branch: yay-machine 1.61M events/s, XState 91.0k events/s; 18× faster. Immediate transitions, routes via last branch: yay-machine 1.27M events/s, XState 89.3k events/s; 14× faster. Event resolution, handled by current state: yay-machine 2.00M events/s, XState 249k events/s; 8.0× faster. Event resolution, falls through to machine-level: yay-machine 2.05M events/s, XState 221k events/s; 9.3× faster. Event resolution, not handled (ignored): yay-machine 28.5M events/s, XState 670k events/s; 43× faster. State effects, entry + exit actions: yay-machine 2.01M events/s, XState 129k events/s; 16× faster. State effects, state-scoped resource + cleanup: yay-machine 1.96M events/s, XState 63.9k events/s; 31× faster. Lifecycle, define a 5-state machine: yay-machine 593k definitions/s, XState 37.4k definitions/s; 16× faster. Lifecycle, create + start + stop: yay-machine 1.18M lifecycles/s, XState 92.7k lifecycles/s; 13× faster. Lifecycle, ... with machine-scoped resource: yay-machine 1.09M lifecycles/s, XState 31.1k lifecycles/s; 35× faster. Batch of short-lived instances, batch of 10: yay-machine 870k lifecycles/s, XState 50.3k lifecycles/s; 17× faster.
yay-machine vs XState: all benchmarks
How many times faster (or leaner) yay-machine is than XState for each benchmark.
Log scale · 1× = parity · bars left of 1× would mean XState is ahead
0.5× 1× 2× 5× 10× 20× 50× REALISTIC WORKLOADS UI: sign-up form form session (38 events) 8.1× Event stream: long-lived connection replay 1,000-event trace 19× Fleet: 1,000 live connections 1 event to each of 1,000 instances 11× Login request login success + logout 14× Login request login rejected + retry 18× Memory per live instance started, idle (disconnected) 2.2× less memory Memory per live instance connected (with heartbeat resource) 3.0× less memory FEATURE MICRO-BENCHMARKS Toggle send TOGGLE 22× Counter INCREMENT + DECREMENT (guards pass) 5.3× Counter INCREMENT beyond max (guard rejects) 11× Guards 1st guard matches 7.8× Guards 3rd guard matches 6.3× Guards default (5th) matches 8.2× Immediate transitions routes via 1st branch 18× Immediate transitions routes via last branch 14× Event resolution handled by current state 8.0× Event resolution falls through to machine-level 9.3× Event resolution not handled (ignored) 43× State effects entry + exit actions 16× State effects state-scoped resource + cleanup 31× Lifecycle define a 5-state machine 16× Lifecycle create + start + stop 13× Lifecycle ... with machine-scoped resource 35× Batch of short-lived instances batch of 10 17×
yay-machine v1.5.0+297 (4179cf1) · xstate 5.33.3 · Bun 1.4.3 · 2026-11-09
11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz, 8 cores, 8 GB · Linux (WSL2) (x64)
See Benchmarks for what each benchmark measures, and how.
The core yay-machine state-machine library has zero production dependencies .
It won’t bloat your node_modules/ and you won’t have to worry about having to patch some security vulnerability or compatibility issue in a 3rd-party package that yay-machine depends on.
We ❤️ TypeScript and want the best experience for TypeScript developers with state-machines.
Each state in yay-machine is a type, giving you more compile-time confidence.