Benchmarks
@opentf/obj-diff is built for two things other diffing libraries trade away: compact diffs and correct handling of native JavaScript values. Every number on this page is generated by benchmarks/collect.mjs and rendered straight from its output — nothing here is typed by hand. Regenerate with bun benchmarks/collect.mjs.
Diff size (fewer operations)
Op counts are deterministic — they don't depend on the machine. This is where compact array diffs pay off: positional differs emit one operation per shifted element, while obj-diff emits one operation per actual edit.
| Edit on a 10,000-element array | obj-diff | microdiff |
|---|---|---|
| Insert 1 element at the front | 1 | 10,001 |
| Insert 1 element in the middle | 1 | 5,001 |
| Remove 1 element from the middle | 1 | 5,000 |
| Remove a run of 10 elements | 10 | 5,000 |
Smaller diffs mean smaller audit logs, smaller sync payloads, and smaller undo stacks — see Use Cases.
Diff speed
Average latency per call across modern diffing libraries; each column uses a single unit so the cells are directly comparable, and the fastest cell in each column is highlighted.
| Library | 1 prop | Mixed | Deep (9×) | Array 10k | TypedArray 10k |
|---|---|---|---|---|---|
| obj-diff | 1.48 µs | 1.84 µs | 2.34 µs | 2.47 ms | 0.019 ms |
| microdiff | 1.89 µs | 1.59 µs | 2.05 µs | 3.03 ms | 4.34 ms |
| deep-diff | 1.41 µs | 1.96 µs | 3.20 µs | 0.553 ms | 62.7 ms |
| deep-diff-ts | 1.11 µs | 1.25 µs | 2.01 µs | 0.742 ms | 5.67 ms |
| deep-object-diff | 4.08 µs | 3.42 µs | 3.67 µs | 8.44 ms | 8.31 ms |
| just-diff | 2.80 µs | 2.83 µs | 4.63 µs | 6.13 ms | 6.45 ms |
| @adobe/optimized-diff | 1.29 µs | 1.73 µs | 3.34 µs | 0.034 ms | 4.90 ms |
| recursive-diff | 2.20 µs | 2.48 µs | 4.33 µs | 3.29 ms | — |
| jsondiffpatch | 1.31 µs | 1.47 µs | 1.80 µs | 160 ms | 5.64 ms |
On the TypedArray column obj-diff is roughly 173× faster than microdiff, because it compares typed arrays natively instead of walking them element-by-element.
A "diff" the size of the data isn't a diff. Speed alone can mislead: a library that emits a single "the whole value changed" replacement (rather than an element-level diff) looks instant but produces a useless result. We discard any cell whose diff serializes to roughly the size of the data itself, showing — instead — that is why
recursive-diffhas noTypedArraytime (it dumps the entire array, since it doesn't understand typed arrays).obj-diffcomputes a minimal, element-level edit script, which is why it is not always the lowest number on the plain-array row but stays honest.
Patch speed
End-to-end diff + apply round-trip, for libraries that can reconstruct the target from their diff. Some need a separate apply package — just-diff pairs with just-diff-apply, fast-json-patch applies its own RFC 6902 ops — whereas @opentf/obj-diff ships diff and patch together.
| Library (diff + patch) | 1 prop | Mixed | Array 10k |
|---|---|---|---|
| obj-diff | 2.67 µs | 3.27 µs | 2.69 ms |
| jsondiffpatch | 3.81 µs | 7.20 µs | 161 ms |
| just-diff + just-diff-apply | 4.70 µs | 5.96 µs | 5.87 ms |
| deep-diff-ts | 4.01 µs | 5.45 µs | 1.58 ms |
| fast-json-patch | 2.71 µs | 3.89 µs | 2.60 ms |
Honest trade-offs
Benchmarks are only useful when they include the cases you don't win:
Small and deeply-nested objects:
obj-diffsits within a whisker of the fastest positional differ — nanoseconds apart, irrelevant at real-world scale.Large plain numeric arrays: computing a minimal edit script (Myers LCS) costs more than a raw scan, so a positional differ can diff a big plain array faster — the payoff is the far smaller diff
obj-diffproduces. When the edit distance exceeds an internal cap,obj-difffalls back to index-by-index comparison to keep worst-case time and memory bounded. See Caveats.
The takeaway: reach for obj-diff when you care about diff quality and correctness across real JS values, not just raw nanoseconds on flat JSON.