The most common real-world pattern: one expression reads one field. name, price, isActive. Each binding is one AST node, one watcher, one field. This page benchmarks that pattern across binding counts from 1,000 to 10,000, for both engine modes. Measured on Apple M4, Node.js v25.4.0.
What these benchmarks measure
Each binding uses a unique field name (f0, f1, …) and a unique model object. This ensures watchers are not shared across bindings — each binding creates its own watcher. This is the realistic worst case for identifier-only bindings: no sharing, maximum isolation.
Three metrics are measured:
Bind time — create the binding, register the watcher, do the initial read. Includes AOT plan lookup (compiled mode) or AST clone (tree mode).
Bulk update time — change every model field; every binding re-evaluates. Worst case: N updates, N re-evaluations.
Single update time — change one model field; one binding re-evaluates. Best case: O(1) regardless of total binding count.
Bind time
Bind time scales linearly with binding count. For identifier-only expressions, compiled and tree mode are within a few percent of each other — the expression is so simple that the compilation overhead in compiled mode roughly equals the AST clone overhead in tree mode. There is no clear winner here; both modes are fast.
TreeCompiled
Bindings
Tree (ms)
Compiled (ms)
Diff
1,000
27.327
30.129
tree 9.3% faster
3,000
92.217
93.439
tree 1.3% faster
5,000
153.982
156.838
tree 1.8% faster
10,000
318.208
356.804
tree 10.8% faster
Bulk update time
When every model field changes at once, every binding re-evaluates. For identifier-only expressions with unique fields this is the O(N) worst case. Even so, 10,000 bindings complete their bulk re-evaluation in under 50 ms in both modes.
At this expression complexity, compiled mode offers a modest improvement — roughly 10–15% faster at 10,000 bindings. The advantage grows significantly for more complex expressions.
TreeCompiled
Bindings
Tree (ms)
Compiled (ms)
Diff
1,000
4.605
5.149
tree 10.6% faster
3,000
13.523
14.596
tree 7.3% faster
5,000
22.606
25.580
tree 11.6% faster
10,000
41.533
48.766
tree 14.8% faster
Single update time
When one field changes, exactly one binding re-evaluates. Because rs-x uses per-field watchers, updating one field does not touch any other binding regardless of how many bindings exist. This is the key property that makes rs-x scale: update cost is O(1) with respect to total binding count.
The single update time stays below 0.1 ms across all measured binding counts — and is essentially the same at 1,000 as at 10,000 bindings. This is the most important number for applications where only a few fields change at a time (which is almost all real applications).
TreeCompiled
Bindings
Tree (ms)
Compiled (ms)
1,000
0.072
0.095
3,000
0.056
0.063
5,000
0.067
0.067
10,000
0.063
0.073
Conclusion: single update time does not grow with binding count. This is the defining property of rs-x's reactive architecture. You only pay the O(N) cost when N fields all change simultaneously — the typical case of one or a few fields changing at a time stays O(1) no matter how large your binding graph is.
Scale reference: bind and update at larger counts
The following table extends to larger binding counts using the tree engine mode. It shows raw bind time and update performance as a reference for capacity planning.
Bindings
Bind (ms)
Bind + init (ms)
Single update (ms)
Bulk update (ms)
100
2.256 ms
371.406 ms
8.375 µs
26.464 ms
500
70.174 ms
95.432 ms
8.083 µs
4.658 ms
1,000
66.185 ms
111.798 ms
1.771 µs
3.010 ms
3,000
161.463 ms
234.292 ms
2.792 µs
37.485 ms
5,000
275.182 ms
309.953 ms
1.708 µs
24.942 ms
10,000
758.932 ms
902.250 ms
1.959 µs
55.082 ms
Bind + init includes the time to complete the initial reactive evaluation cycle after binding — i.e. until every expression has its first value. This is typically higher than raw bind time because it waits for the reactive scheduler to settle.