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";
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
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-fastoffsets into anInt32Arrayinstead, 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
- npm: https://www.npmjs.com/package/@libs-jd/diff-fast
- GitHub: https://github.com/jeet-dhandha/diff-fast
MIT licensed. If you diff big things, swap the import and let me know how it goes.
Top comments (0)