Delta libraries: how diffing works and which library to use
Every time you run git diff, review a pull request, or undo a change in a collaborative document, a diff algorithm is doing the heavy lifting. These algorithms compute the "delta" between two versions of something, whether that is text, a JSON object, or a binary file. The concept is simple. The implementation gets interesting fast.
I have been working with diff and patch patterns in a few projects, and the ecosystem of libraries is broader than most people realize. Here is what I have learned about how they work and which ones are worth using.
What a delta actually is
A delta is the difference between two states. Instead of storing or transmitting a complete copy of something, you store just what changed. If you edit three words in a 10,000-word document, the delta is those three word changes, not the entire document.
This idea shows up at every level of software. Text diffs compare lines or characters. JSON diffs compare object structures. Binary diffs compare raw bytes. The algorithm and output format differ, but the core concept is the same: represent the minimum set of changes needed to transform version A into version B.
The algorithms behind it
Most text diffing libraries use the Myers algorithm, published by Eugene Myers in 1986. Git uses it as the default. So does VS Code. The algorithm finds the shortest edit script (the smallest number of insertions and deletions) to transform one sequence into another. It runs in O(ND) time, where N is the total length of both inputs and D is the number of differences. When the differences are small relative to the total size, it is fast.
Git actually offers four diff algorithms. Myers is the default. Patience diff anchors on unique matching lines first (like function signatures), which tends to produce more human-readable output for code. Histogram extends Patience and is generally faster. Minimal spends extra time guaranteeing the absolute smallest possible diff.
For JSON diffing, the approach is different. Instead of comparing characters or lines, these algorithms walk the object tree and compare values at each path. Array diffing is the hard part, because the algorithm needs to detect insertions, deletions, and moves within ordered lists. Libraries like jsondiffpatch use the Longest Common Subsequence (LCS) algorithm for arrays, which is closely related to what Myers does for text.
Text diffing: diff (jsdiff)
The diff package on npm (commonly called jsdiff) is the dominant text diffing library in JavaScript. It pulls around 380 million downloads per month and supports character-level, word-level, line-level, and sentence-level diffing. It can also generate and apply unified patches in the same format as git diff.
For most text diffing needs, this is the one to use. It is well-maintained, the API is straightforward, and it handles edge cases that simpler implementations miss. The bundle size is about 6kb gzipped, which is reasonable for what it does.
If you need something smaller and are only doing simple string-to-string diffs, fast-diff is worth a look. It is the diff engine used inside the Quill rich text editor, and it is tiny. But it only does character-level diffing with no patch generation, so jsdiff is the more versatile choice.
JSON diffing: the options
JSON diffing is where the library landscape gets crowded. The right choice depends on what you need.
jsondiffpatch is the most feature-rich option. It handles nested objects, detects array moves (not just insertions and deletions), can diff long strings using text diff internally, and outputs in multiple formats including RFC 6902 JSON Patch. It also supports patching and reverse patching, so you can apply a diff forward or undo it. If you need a full-featured JSON differ, this is it.
fast-json-patch is leaner and strictly follows the RFC 6902 standard. It has a useful observe/generate pattern where you watch an object for changes and it generates patches automatically. This is great for network synchronization, where you need to send minimal updates over a websocket. About 25 million monthly downloads.
microdiff is the speed and size champion. At 0.5kb gzipped, it is almost nothing. Benchmarks show it running 2-4x faster than deep-diff on typical objects. The tradeoff is a minimal API surface. It tells you what changed but does not apply patches or generate RFC 6902 output. For cases where you just need to know what is different between two objects (like deciding whether to re-render a component), microdiff is excellent.
deep-diff is mature and widely used (12 million monthly downloads) but has not been updated since 2019. No ESM support, no TypeScript types. It still works, but I would not start a new project with it.
State management: Immer and patches
Immer is not a diff library in the traditional sense, but its produceWithPatches function generates JSON patches that describe how state changed. If you use Redux Toolkit, you are already using Immer under the hood. The patches include inverse patches for undo support, which makes implementing undo/redo straightforward.
The performance caveat is real though. Immer's patch generation runs at about 5-6 operations per second in benchmarks. Mutative, a drop-in replacement, hits over 1,000 operations per second for the same workload. If you are generating patches at high frequency (like tracking every keystroke), Mutative is worth considering.
Collaborative editing: CRDTs and OT
When you need multiple users editing the same document simultaneously, simple diffing is not enough. You need a conflict resolution strategy.
Operational Transform (OT) is the older approach. Google Docs uses it. Each edit becomes an operation, a central server establishes ordering, and clients transform incoming operations against their pending local changes. The Quill Delta format is an OT-compatible representation of rich text changes.
CRDTs (Conflict-free Replicated Data Types) are the newer approach. They are data structures with mathematical guarantees that concurrent modifications always converge without a central server. Yjs is the most popular CRDT library (21,000+ GitHub stars, 13 million monthly downloads) with bindings for CodeMirror, Monaco, Quill, and ProseMirror. Automerge offers a cleaner API that feels like working with regular JavaScript objects. Loro is a newer Rust-based option focused on performance and avoiding text interleaving anomalies.
The tradeoff between OT and CRDTs is complexity versus infrastructure. OT requires a central server but the algorithms are simpler. CRDTs work peer-to-peer and offline but the data structures are more complex and can use more memory.
Which one to pick
For text diffs, use diff (jsdiff). It is the standard for a reason.
For JSON diffs where you need features (array moves, nested objects, patch/unpatch), use jsondiffpatch. If you need RFC 6902 compliance for API interop, use fast-json-patch. If you just need to detect changes with minimal overhead, use microdiff.
For state management patches, Immer if you are already in that ecosystem, Mutative if performance matters.
For collaborative editing, Yjs is the safe choice with the largest ecosystem. Automerge if you prefer a cleaner API. Loro if you are okay with a younger project and want the best performance characteristics.
The key insight is that "diffing" is not one problem. Text diffing, structural diffing, and collaborative conflict resolution are fundamentally different challenges that happen to share the concept of "what changed." Picking the right library starts with understanding which problem you are actually solving.
Sources
Related posts
Why I built Omnibase: a universal database MCP server
I got tired of copy-pasting query results between DataGrip and AI agents. So I built an MCP server that gives AI agents secure, direct access to any database.
Offline-first apps: harder than it sounds
Building apps that work without internet is one of those things that seems straightforward until you actually try it. Here is what makes it hard and how to approach it.
Deploying apps with Railway
How Railway makes deploying web applications simple without giving up control, and why I use it for side projects.
Enjoying the blog? Subscribe via RSS to get new posts in your reader.
Subscribe via RSS