Benchmarks
How neotraverse compares to the original traverse, across the full operation × shape matrix. Toggle between throughput (higher is better) and memory (lower is better).
Geometric mean speedup vs
traverse: functionalneotraverse≈ 4.91×,neotraverse/legacy≈ 2.97×.Geometric mean allocation reduction on core walks (
forEach,map,clone,reduce,paths,nodes): functionalneotraverse≈ 6.22× less,neotraverse/legacy≈ 1.97× less (traverse B/op ÷ neotraverse B/op).
These numbers come from packages/neotraverse/bench/run.ts (powered by tinybench) and are imported directly from the committed bench/results.json. Reproduce locally with pnpm bench.
Generated 2026-06-08 · Node v24.1.0 · neotraverse 1.0.0 vs traverse 0.6.11
These are runtime benchmarks.
Bundle size (tree-shaken, brotli)
The default neotraverse export is utility-first (sideEffects: false): your bundler drops unused exports.
| Scenario | Brotli (approx.) |
|---|---|
One walk terminal (forEach, map, find, …) | ~1.9 KB |
Path helpers only (get / has / set) | ~0.3 KB |
All functions except deprecated Traverse | ~5.7 KB |
Range: ~2–6 KB brotli (tree-shaken), floor ≈ single traversal, ceiling ≈ full toolkit.
Source: bench/bundle-sizes.json (pnpm bundle-size in packages/neotraverse). For a third-party traverse vs neotraverse comparison, see bundle-roast ↗.
forEach
map
clone
reduce
paths
nodes
get
has
set
Notes
- Traversal operations (
forEach,map,clone,reduce,paths,nodes) on the functional defaultneotraverseexport average ~5.6× vstraverseand peak at ~10× (clone · small), with ~5.7× less heap per op on average (up to ~11× onforEach · wide). Theneotraverse/legacydrop-in averages ~3× speed and ~2× less allocation across core walks. clonepeaks at ~10× on the functional API (clone · small). The legacy build and the functional API share the sameclone()/copy()source, but the functional bundle inlines a leaner walk, so it often wins by a wide margin on smaller shapes while wide flat objects stay closer (~3×).- The
get/has/setpath helpers are micro-ops (20M+ ops/s); the functionalneotraverseAPI and theneotraverse/legacydrop-in are both within noise oftraverse. The legacy class stores its state in plain instance fields (not#private, which would downlevel to WeakMaps at ES2015), so it stays native-fast here too. - Memory figures are an approximate bytes-allocated-per-op signal (median of GC-bracketed samples). JS memory measurement is noisy, treat them as ballpark, like the throughput margins of error (
rme) in the JSON.
neotraverse/safe — the safety trade-off
neotraverse/safe is a separate, opt-in core built on an iterative engine. It is not a faster successor — it trades a little throughput for stack-safety and bounded memory on partial consumption. The point of these numbers is when to reach for it, not "/safe is faster".
Throughput
On a full eager scan, /safe's lazy iterator runs at roughly 0.8× the default neotraverse (which is itself ~5× faster than traverse) — so /safe is still ~4× faster than the original traverse. clone is at parity; in-place set is slightly ahead. Reproduce with pnpm bench:safe (written to bench/results-safe.json).
Memory & stack-safety (the reasons it exists)
Reproduce with pnpm bench:safe-memory · 100,000-node tree · Node v24.1.0
| Scenario | default neotraverse | neotraverse/safe | Verdict |
|---|---|---|---|
| Deep input (linked tree, levels) | overflows past ~2,000 💥 | 200,000+, no overflow | /safe only |
| First 5 of a filtered scan (peak MB) | 19.4 MB | 3.1 MB | /safe 6.3× less |
| Materialize the whole tree (peak MB) | 42.6 MB | 77 MB | default wins (1.8× more) |
Read this honestly:
- Stack safety is categorical. The default walk is recursive and overflows on deep input;
/safeis iterative and doesn't. For untrusted JSON or naturally deep data (ASTs, file trees), this is a correctness/security difference, not a speed one. - Early-exit / streaming uses far less memory — because
/safe's lazy chains never materialize the intermediate collection that the default's array-returningfilter/paths/nodesmust build first. This win is algorithmic, so it's stable. - Full materialization is not a memory win.
/safeallocates oneVisitper node, so keeping the whole tree costs more. Its memory advantage is specifically about not materializing what you don't consume.
See Why /safe for the full story.