DEV Community

Cover image for diff-fast: a drop-in faster jsdiff (2.6–4.8 , identical output)
Jeet Dhandha
Jeet Dhandha

Posted on • Originally published at npmjs.com

diff-fast: a drop-in faster jsdiff (2.6–4.8 , identical output)

If you render diffs, do three-way merges, or build review tooling in JavaScript, there's a good chance jsdiff (the diff package) is already in your dependency tree — it's one of the most-depended-on packages on npm. Its Myers-diff core is correct and battle-tested. But the hot loop leaves real speed on the table.

diff-fast is a drop-in replacement that reclaims it — 2.6×–4.8× faster on the common diffChars / diffLines paths, with byte-for-byte identical output.

Drop-in means drop-in

It re-exports the entire diff API unchanged and only shadows diffChars and diffLines with a faster implementation. Same function signatures, same options, same return objects. The whole migration is one line:

// before
import { diffChars, diffLines } from "diff";

// after
import { diffChars, diffLines } from "@libs-jd/diff-fast";
Enter fullscreen mode Exit fullscreen mode

Everything else in the API (diffWords, applyPatch, structuredPatch, …) is passed straight through to the real diff, which is a runtime dependency — so you're never running an incomplete fork.

npm i @libs-jd/diff-fast
Enter fullscreen mode Exit fullscreen mode

The benchmark

Measured on Node v24 / V8, best-of-7, against diff@9 — comparing element-for-element identical output on every case:

workload jsdiff diff-fast speedup
diffChars, 600 chars, ~15% edits 3.06 ms 0.63 ms 4.8×
diffChars, 1.2k chars, ~8% edits 3.55 ms 0.97 ms 3.7×
diffLines, 400 lines, ~10% edits 1.08 ms 0.34 ms 3.2×
diffLines, 150 lines, ~20% edits 0.51 ms 0.19 ms 2.6×

What actually changed

No new algorithm — same Myers diff, same result. The wins are all in how the inner loop touches memory:

  • Dense diagonal in a typed array. jsdiff keys its furthest-reaching paths in a plain array indexed by a signed diagonal, so negative indices push it into dictionary (hash) mode. diff-fast offsets into an Int32Array instead, keeping the whole search in packed, unboxed elements.
  • A flat component pool instead of allocating a fresh {count, value, added, removed} object on every edit step, which turns O(d²) object churn into index bumps.
  • Dropped per-iteration bookkeeping (including a Date.now() call jsdiff makes on the default path).

The part that matters: identical output

A speedup you can't trust is worthless. diff-fast ships with a 200-case gate (character and line diffs, random edit rates, identical inputs, insert/delete/replace mixes) that asserts the fast output is element-for-element identical to jsdiff via JSON.stringify equality — including the exact key order of the component objects. 0 mismatches. That gate runs in CI on every change.

One honest caveat: on tiny or identical inputs the diff early-exits, so there diff-fast is roughly neutral (~1×). The win shows up on real-sized diffs, which is where it counts.

Links

MIT licensed. If you diff big things, swap the import and let me know how it goes.

Top comments (0)