Skip to content

Benchmark

The hard case is thousands of machines reacting to thousands of events inside one frame budget — trading terminals, live order books, monitoring walls, dense collaborative canvases. That’s what this engine is built for.

Watch it live — all three engines driving a real grid in your browser. Watch which ones fall behind as the load climbs.

DunkyXStateZag
Event throughput7.2 M ops/s897 Kn/a ᵃ
Memory, 2-field context3.6 KB/machine3.6 KB9.1 KB
Memory, 64-field context4.1 KB/machine4.1 KB134 KB
Re-render wall, 1000 rows3.9 ms6.8 msn/a ᵃ

ᵃ Zag’s send is microtask-batched — can’t run in a synchronous ops/sec loop.

A single machine, one event, tight loop:

ops/sec
Dunky7.2 M
XState898 K
Zagn/a ᵃ

XState allocates a new immutable snapshot on every transition. Dunky mutates context in place — a transition allocates nothing.

~8× faster at ignoring writes nobody is watching

Section titled “~8× faster at ignoring writes nobody is watching”

Change a field no observer has selected. The dedup layer re-evaluates and value-compares — no listener fires:

Irrelevant write, N observersDunkyXState
1 0004.5 M ops/s536 K
5 0001.9 M ops/s453 K

XState’s coarse actor.subscribe fires on every snapshot change. You have to hand-write a differ to match this — which is what the xstate column in the full tables already accounts for.

One machine, 5 000 observers, bump one field:

Change 1 of N observersDunkyXState
100325 K253 K
1 00010.7 K10.7 K
5 0007.9 K741

Roughly par at small N. At 5 000 observers XState degrades ~10× harder — its coarse subscribe has no way to skip unaffected listeners.

The whole point of the plain-object model: memory grows with your data, not with the framework’s bookkeeping.

Context widthDunkyXStateZag
2 fields3.6 KB3.6 KB9.1 KB
64 fields4.1 KB4.1 KB134 KB

Going 2 → 64 fields costs Dunky ~0.5 KB/machine. Zag allocates one reactive cell per field — 64-field context balloons to 134 KB/machine, ~33× more.

Spin-up cost per machine:

µs / machine
XState1.95
Dunky2.42
Zag8.16

XState cold-starts ~1.2× faster. Zag is ~3.4× slower than both. Dunky’s bet is flat memory and hot-path throughput, not spin-up speed.

These loads are extreme by design — they only reflect real software when a single view holds thousands of independently-stateful, live-updating cells in one frame budget:

WorkloadLive cells
Full L2 order book (Bookmap, Sierra Chart)3k – 15k
Options chain / vol surface (thinkorswim, Tastytrade)5k – 20k
Screeners / heatmaps (TradingView, Finviz)3k – 15k
Live-data spreadsheets (Excel + market feeds)5k – 20k
Observability walls (Grafana, Datadog)5k – 50k
Dense collaborative canvases (Figma, Miro, tldraw)5k – 50k

The sharpest target is a dense live financial surface: 5k–20k live cells, updating tens of thousands of times per second, often needed on web and native from one codebase — where every property this benchmark measures pays at once.

Full methodology, fairness notes, and all per-scenario tables in the benchmark README.