microdiff

A tiny, zero-dependency library for deep diffing objects and arrays with full TypeScript support.

Library
npm
v1.6.0
3,867stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
56/100Fair
Development Activity44
Maintenance36
Community48
Maturity56
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
63/100Good
Architecture72
Code Quality65
Innovation70
Learning Curve45

Microdiff is a sub-1kb JavaScript library that computes the difference between two objects or arrays, returning a flat list of CREATE, REMOVE, and CHANGE operations along with the path to each changed property. It has zero runtime dependencies, ships full TypeScript type definitions, and runs identically across Node, Deno, Bun, browsers, and service workers.

Under the hood, Microdiff walks both objects in a single recursive pass, using strict equality augmented with special-cased handling for Date, RegExp, String/Number wrapper objects, and Temporal types. An optional cycle-detection stack (enabled by default) lets it safely diff self-referential structures without infinite recursion, and it can be disabled via the cyclesFix: false option for a speed boost when inputs are known to be cycle-free, such as parsed JSON.

What You Get

  • A single diff(oldObj, newObj, options) function that returns an array of typed difference records
  • TypeScript type definitions for Difference, DifferenceCreate, DifferenceRemove, and DifferenceChange out of the box
  • Built-in cycle detection for self-referential objects, toggleable via the cyclesFix option
  • Special-case handling for Date, RegExp, and Temporal values so semantically equal instances aren’t reported as changed
  • Zero runtime dependencies and a sub-1kb minified/gzipped footprint

Common Use Cases

  • Detecting which fields changed between two versions of a form or a Redux/Zustand-style state snapshot
  • Building undo/redo or audit-log systems that need a precise path to each mutation
  • Comparing API response payloads in tests to assert only expected fields changed
  • Powering real-time sync engines that need to ship minimal changesets over the wire

Under The Hood

Architecture The entire library is one recursive diff() function exported from index.ts: it iterates the keys of the old value looking for removed or changed entries, recurses into nested objects/arrays that share a compatible shape, then does a second pass over the new value’s keys to find additions. There’s no internal layering because the problem doesn’t need one — recursion handles nesting, and a _stack array (not part of the public options) threads cycle-detection state through recursive calls without a class or external state container. This keeps the implementation compact, though the dense conditional logic mixing type detection, rich-type comparison, and recursion in one function block makes it slightly harder to extend without touching several branches at once.

Tech Stack The TypeScript source is compiled twice by the same tsc invocation — once targeting CommonJS, once targeting ES2020 — with shx renaming the CJS build’s output extensions to .cjs/.d.cts so npm’s exports map can serve both module systems from a single source file. The package depends on zero runtime packages; devDependencies are limited to prettier for formatting, terser for measuring minified/gzipped size, and mitata for benchmarking against competing diff libraries (deep-diff, deep-object-diff, diff) in bench.js.

Code Quality Tests run on Node’s built-in node:test runner with assert.deepStrictEqual — no external test framework — split into dedicated files per concern (basic.js, arrays.js, dates.js, regex.js, cycles.js, nan.js, class-primitives.js, temporal.js) that cover edge cases like null-prototype objects, self-referential cycles, NaN equality, and the newer Temporal API. There is no GitHub Actions CI workflow in .github/ (only issue templates), so this test suite isn’t automatically enforced on push or PR. TypeScript’s declaration: true output gives consumers full type coverage, and the single-file source keeps naming consistent, though there’s no explicit input validation — the function assumes well-formed object/array arguments.

API Design The public surface is one default export, diff(oldObj, newObj, options), so getting started requires a single import and a single call with no configuration. The only knob exposed is cyclesFix, a clear opt-out for trading safety against speed when inputs are known to be cycle-free. Output is more granular than most competing diff libraries — each change carries a path array pinpointing exactly where it occurred rather than just a whole-value comparison — and the README backs its speed claims with its own benchmark numbers against deep-diff, deep-object-diff, and jsDiff.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search