Zustand-Focused Benchmarks
July 12, 2026 ยท View on GitHub
This document records the benchmark scenarios used when positioning Coaction against Zustand. The numbers should be regenerated locally before publishing a claim, because JavaScript microbenchmarks vary by runtime, CPU, package version, and state shape.
Existing Update Benchmark
The root README chart is generated from:
pnpm benchmark
That benchmark compares update throughput for:
- Coaction object replacement
- Coaction mutable draft updates through Mutative
- Zustand object replacement
- Zustand with Immer
It is useful for explaining why Coaction can keep mutable update DX without following Zustand's Immer performance profile.
Derived-State Positioning Benchmark
Run:
pnpm benchmark:zustand-positioning
The script covers two scenarios:
- stable derived reads
- update then read derived value
The comparison includes:
- Coaction accessor getter cached by the built-in
alien-signalsruntime - Coaction
get(deps, selector)computed value - Zustand selector that recomputes derived data
- Zustand manually maintained
totalfield
The maintained Zustand field is included intentionally. It is the fastest way to read a derived value in Zustand, but it shifts consistency work into actions. Coaction's value proposition is that cached derived state is part of the store runtime instead of a field that application code must keep synchronized.
The update-plus-read cases also enforce Coaction's immutable public-state
boundary. External reads remain behind readonly proxies so actions cannot
mutate nested values outside set(). Cached getter evaluation uses a separate
frozen snapshot: its first evaluation snapshots the immutable state and later
updates apply only the paths reported by Mutative. This keeps computed traversal
safe without paying one proxy trap per array element and field. Stable cached
reads and large Mutative updates remain separate cases so regressions in those
paths are visible independently.
The protected-read implementation was measured before and after the snapshot change on the same machine. The two update-plus-read cases moved from roughly 5,900 ops/sec to about 50,000โ65,000 ops/sec, while the large update case remained independently gated. The cached snapshot adds about 0.9 KiB gzip to the local entry. These numbers document the reviewed performance/size tradeoff; they are not cross-machine performance claims.
The blocking regression check uses the transport-free coaction/local entry:
pnpm build
pnpm benchmark:check
Its thresholds are regression floors with headroom for CI variance, not publishable performance claims. Any threshold change requires a reviewed runtime-semantic or benchmark-methodology change; a failing gate must not be silenced by rebaselining alone.
How to Interpret Results
Do not publish one benchmark as a universal statement that one library is always faster.
Use the update benchmark for this claim:
Coaction's built-in mutable update path can avoid the cost profile of Zustand + Immer in large immutable updates.
Use the derived-state benchmark for this claim:
Coaction has a built-in cached derived-state runtime. Zustand can match constant-time reads by manually storing derived values, but the application must maintain those values consistently.
Future Benchmark Candidates
The next useful comparisons are:
- React rerender count for unrelated updates
- selector-heavy component trees
- bundle size for minimal stores and feature-rich stores
- worker/shared-mode round trip latency
- adapter write propagation for
@coaction/zustand
Keep benchmark scripts small and source-controlled. Generated images should only be committed when the README or documentation references their exact numbers.