ShopperCove
Menu
All writingBlogTopicsCategoriesAboutRSS
Blog
Categories
Observability & SRE62All categories
About

Plate 68

  1. Blog

Defu vs lodash.merge: 6x Faster, 452 B vs 4 KB

Aditya Challa·5 October 2026·5 min read

Hands-on
On this page
  1. What I tested
  2. How much smaller is defu?
  3. Is defu faster than lodash.merge?
  4. Do arrays and null behave the same?
  5. Which one should you pick?
  6. Sources
  7. Related

I timed nested config merges on this box: defu 6.1.7 finished about 430,000–475,000 merges per second, while lodash.merge 4.6.2 managed about 75,000–77,000. That is roughly 5.5–6× faster, for 452 B gzip instead of 4.1 KB.

Short answer: use defu when you want a tiny defaults helper that leaves your defaults object alone and skips null/undefined overlays. Reach for lodash.merge only if you already ship lodash, or you rely on its index-by-index array merge and null-overwrites-default rules. No affiliate links in this post.

What I tested

  • Machine: 8 vCPU Intel Xeon / 15 GB RAM shared Linux cloud box, tested 6 Oct 2026 about 01:45 IST.
  • Versions: defu 6.1.7, lodash.merge 4.6.2, Node 22.20.0, esbuild 0.28.2 browser ESM minify + gzip -9.
  • Fixture: /workspace/bench-defu-lodash-merge/. Nested config (server/build/features/meta) merged 50,000 times per round, 9 rounds, median, after warm-up. I ran it twice with the order reversed. Shallow Object.assign and object-spread were baselines only.
  • Findings: defu 427,847 and 475,272 ops/s; lodash.merge 77,085 and 75,387 ops/s. Bundle: defu 827 B minified / 452 B gzip; lodash.merge 10,647 B / 4,119 B. defu left the defaults object unchanged; lodash.merge mutated its first argument.
  • One surprise: defu concatenates arrays by default (overlay tags ["c"] plus defaults ["a","b"] became ["c","a","b"]), while lodash.merge merged by index (["c","b"]). null in the overlay was ignored by defu and applied by lodash.merge.
  • Not tested: full lodash (only the lodash.merge package), defuFn customizers beyond a replace-array helper, circular objects, class instances, browsers, Bun or Deno.

How much smaller is defu?

Build (esbuild browser minify)Minifiedgzip -9
defu 6.1.7827 B452 B
lodash.merge 4.6.210,647 B4,119 B

About 9× smaller on gzip. If you already carry lodash for other helpers, the merge export is effectively free and this table does not apply. For a single utility, size-limit vs bundlesize will flag the 4 KB. Broader lodash trimming is covered in es-toolkit vs lodash.

Is defu faster than lodash.merge?

Nested config merge (median of 9)defulodash.mergeObject.assign shallowspread shallow
run 1 (defu first)427,847 ops/s77,085 ops/s9.44M ops/s13.4M ops/s
run 2 (order reversed)475,272 ops/s75,387 ops/s8.85M ops/s11.7M ops/s

Yes, by about 5.5–6× on this fixture. Shallow assign/spread stay in the millions of ops/s because they do not walk nested keys; they are not a substitute when you need deep defaults. I used a Nuxt-ish config shape (server, build, features, meta) so the walk depth matches what Vite/Nuxt plugins actually merge.

Do arrays and null behave the same?

Casedefulodash.merge
Overlay { tags: ["c"] } on defaults { tags: ["a","b"] }["c","a","b"] (concat)["c","b"] (index merge)
Overlay { a: null } on { a: 1, b: 2 }{ a: 1, b: 2 } (null skipped){ a: null, b: 2 }
Overlay { a: undefined }defaults keptdefaults kept
Nested keep of sibling keyskeptkept
Defaults object after callunchangedfirst arg mutated

These rules matter more than the speed gap. If your config uses arrays as replaceable lists (plugin lists, tag lists), default defu will grow them. createDefu can replace arrays instead; I verified that custom path returned ["c"] only. For schema-level defaults with clearer types, compare notes with Zod vs Valibot.

Which one should you pick?

SituationMy pick
Nuxt / unjs-style defaults, tiny bundledefu
Need null to clear a defaultlodash.merge
Arrays should replace, not growcreateDefu replace rule, or lodash with care
Already ship lodashlodash.merge
Shallow one-level options onlyObject.assign / spread

Bottom line: defu was about 6× faster and about 9× smaller than lodash.merge on this box, and it does not mutate your defaults. Who should not switch: code that depends on lodash.merge writing nulls into the result or merging array slots by index. Drop unused deps with knip vs depcheck, and keep date helpers honest with dayjs vs date-fns if those sit next to your config layer.

How this was made: I installed defu and lodash.merge on the ShopperCove box, measured esbuild minify+gzip sizes, timed nested merges in two order-reversed runs, checked array/null/mutation behavior (including a createDefu array-replace helper), and saved the JSON results. The write-up was drafted with AI help and checked against that output.

Sources

  • https://github.com/unjs/defu
  • https://www.npmjs.com/package/defu
  • https://www.npmjs.com/package/lodash.merge
  • https://lodash.com/docs/#merge

Related

  • https://www.shoppercove.com/blog/es-toolkit-vs-lodash
  • https://www.shoppercove.com/blog/size-limit-vs-bundlesize
  • https://www.shoppercove.com/blog/knip-vs-depcheck
  • https://www.shoppercove.com/blog/zod-vs-valibot
  • https://www.shoppercove.com/blog/dayjs-vs-date-fns
  • https://www.shoppercove.com/blog/picocolors-vs-chalk
  • https://www.shoppercove.com/blog/ncu-vs-npm-outdated
  • https://www.shoppercove.com/blog/tinyexec-vs-execa
defulodash.mergejavascriptperformancebundle sizeconfigurationmerging

Lab evidence

What I found running this

Hands-on on ShopperCove box 6 Oct 2026 ~01:45 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 22.20.0). defu 6.1.7 vs lodash.merge 4.6.2, esbuild 0.28.2 browser ESM minify + gzip -9. Fixture /workspace/bench-defu-lodash-merge/. Size: defu 827/452 B vs lodash.merge 10,647/4,119 B. Speed (50k nested merges, 9-round median, order-reversed runs): defu 427,847 / 475,272 ops/s; lodash.merge 77,085 / 75,387; Object.assign 9.44M / 8.85M; spread 13.4M / 11.7M. Behavior (res-behavior.json): defu concat arrays ["c","a","b"] vs lodash index-merge ["c","b"]; defu skips null (keeps default 1) while lodash writes null; both skip undefined; defu defaults unchanged; lodash.merge mutates target; createDefu replace-array returned ["c"] only. Raw: res-bundle.json, res-speed-run1.json, res-speed-run2.json, res-behavior.json. Not tested: full lodash, circular objects, class instances, browsers, Bun/Deno. No affiliate.

Notes when a lab post goes up

Occasional email for new hands-on reviews. No sequence and no sponsors.

Related links

  • Plate 55

    Klona vs structuredClone: 1.4M vs 225k Clones per Second

    5 Oct 2026

  • Plate 91

    Tinyexec vs execa: 2.7 KB vs 37 KB Gzip

    5 Oct 2026

  • Plate 78

    Clsx vs classnames: 294 B vs 857 B Gzip

    5 Oct 2026

On this page

  1. What I tested
  2. How much smaller is defu?
  3. Is defu faster than lodash.merge?
  4. Do arrays and null behave the same?
  5. Which one should you pick?
  6. Sources
  7. Related
All writingBlogCategoriesTopicsAboutPrivacyRSS

© 2026 ShopperCove