Performance
These benchmark the framework-agnostic core engine (atoms, selectors, families, transactions) head-to-head against Jotai v2.20.2 on the same CI runner, across two JavaScript engines: Bun (JavaScriptCore / Safari) and Node.js (V8 / Chrome). The figures below are the latest main run — refreshed automatically, nothing is excluded.
They measure the shared engine, not framework rendering. The React, Vue, Svelte, Solid, and Angular adapters build on each framework's own reactivity — Valdres rides on top of it rather than trying to outrun it, so adapter performance is bounded by the framework itself.
Atoms
| Benchmark | Safari (JSC) | Chrome (V8) |
|---|---|---|
| atom lifecycle (create+100get+100set) | 16.3x faster | 2.9x faster |
| atom(1) | 15.5x faster | 2.3x faster |
| get 1000 atoms | 30.2x faster | 6.8x faster |
| set 1000 atoms | 7.3x faster | 2.9x faster |
| set(atom, curr => curr+1) | 18.4x faster | 3.3x faster |
| set(atom, value) | 12.1x faster | 2.8x faster |
| set(atom) with 10 subs | 10.7x faster | 3.0x faster |
| store.get(atom) | 7.3x faster | 5.3x faster |
Selectors
| Benchmark | Safari (JSC) | Chrome (V8) |
|---|---|---|
| selector(fn) | 8.4x faster | 1.5x slower |
| set + read 10 selectors | 3.8x faster | 1.3x faster |
| set + read 100 selectorFamily entries | 2.6x faster | 1.0x slower |
| set + read 100 selectors | 4.6x faster | 1.0x slower |
| set + read through 5 chained selectors | 3.0x faster | 1.3x faster |
Transactions
| Benchmark | Safari (JSC) | Chrome (V8) |
|---|---|---|
| txn: 10 atoms × 10 selectors, set + read | 1.6x faster | 1.0x faster |
| txn: 10 atoms × 10 selectors, with subs | 5.0x faster | 2.7x faster |
| txn: 10 atoms × 100 selectors, set + read | 3.3x faster | 1.0x slower |
| txn: asymmetric DAG shared sink | 4.1x faster | 1.7x faster |
| txn: cross-atom 1000 selectors, set + read | 4.1x faster | 1.1x faster |
| txn: cross-atom 1000 selectors, with subs | 16.2x faster | 9.3x faster |
| txn: large asymmetric DAG (1000 leaves × 50 chain) | 5.1x faster | 2.0x faster |
Store
| Benchmark | Safari (JSC) | Chrome (V8) |
|---|---|---|
| createStore | 16.2x faster | 1.3x faster |
| sub + unsub | 4.2x faster | 1.0x slower |
| sub+unsub on chain of 100 unsubscribed derived deps | 1.0x faster | 1.7x slower |
| sub+unsub on chain of 50 unsubscribed derived deps | 1.3x faster | 1.9x slower |
| sub+unsub on chain of 500 unsubscribed derived deps | 1.1x faster | 1.7x slower |
Families
| Benchmark | Safari (JSC) | Chrome (V8) |
|---|---|---|
| atomFamily: txn update 5,000 existing members | 3.2x faster | 1.4x faster |
| atomFamily(id) | 1.4x faster | 1.7x faster |
| atomFamily(id) cache hit | 1.5x slower | 3.8x slower |
| atomFamily(string) cache hit | 1.3x slower | 4.8x slower |
| selectorFamily(id) | 1.5x faster | 7.1x slower |
| selectorFamily(string) cache hit | 2.1x slower | 2.5x slower |
Other
| Benchmark | Safari (JSC) | Chrome (V8) |
|---|---|---|
| subscribe + unsubscribe 100 shared selector pairs | 1.0x faster | 2.6x slower |
| subscribe + unsubscribe 100 shared selector pairs + fan-in | 1.2x faster | 1.9x slower |
| subscribe + unsubscribe 100 shared selector pairs + fan-in + mounted spine | 1.8x slower | 3.5x slower |
Honest accounting — these are the operations where Valdres is currently slower. They're tracked as optimization targets:
selector(fn)— Chrome (V8): 1.5x slowerset + read 100 selectorFamily entries— Chrome (V8): 1.0x slowerset + read 100 selectors— Chrome (V8): 1.0x slowertxn: 10 atoms × 100 selectors, set + read— Chrome (V8): 1.0x slowersub + unsub— Chrome (V8): 1.0x slowersub+unsub on chain of 100 unsubscribed derived deps— Chrome (V8): 1.7x slowersub+unsub on chain of 50 unsubscribed derived deps— Chrome (V8): 1.9x slowersub+unsub on chain of 500 unsubscribed derived deps— Chrome (V8): 1.7x sloweratomFamily(id) cache hit— Safari (JSC): 1.5x sloweratomFamily(id) cache hit— Chrome (V8): 3.8x sloweratomFamily(string) cache hit— Safari (JSC): 1.3x sloweratomFamily(string) cache hit— Chrome (V8): 4.8x slowerselectorFamily(id)— Chrome (V8): 7.1x slowerselectorFamily(string) cache hit— Safari (JSC): 2.1x slowerselectorFamily(string) cache hit— Chrome (V8): 2.5x slowersubscribe + unsubscribe 100 shared selector pairs— Chrome (V8): 2.6x slowersubscribe + unsubscribe 100 shared selector pairs + fan-in— Chrome (V8): 1.9x slowersubscribe + unsubscribe 100 shared selector pairs + fan-in + mounted spine— Safari (JSC): 1.8x slowersubscribe + unsubscribe 100 shared selector pairs + fan-in + mounted spine— Chrome (V8): 3.5x slower
Why the core is fast
The engine is optimized for minimal allocations and cache-friendly data structures — WeakMap-based storage with no intermediate objects, and transactions that batch updates so subscribers fire once.
In React specifically, the adapter is built on useSyncExternalStore (where Jotai uses useEffect + useReducer), which avoids extra renders and effect-cleanup overhead. The other adapters integrate with each framework's native reactivity — Vue refs, Svelte runes, Solid signals, Angular signals — so there the relevant performance is the framework's own.
Running benchmarks locally
# Bun (JSC/Safari engine)
bun run --cwd packages/valdres test:bench
# Node.js (V8/Chrome engine)
bun run --cwd packages/valdres test:bench:node
Historical trends
Performance is tracked over time on every push to main. View the live, always-current numbers and history at bencher.dev/perf/valdres.