Plate 17
Immer vs Mutative: 86 us vs 3 us per Update
Aditya Challa4 min read
I bumped one user score in a 1,000-user tree on this box: Immer 11.1.21 median about 86 us per produce, Mutative 1.3.0 about 3 us. Both kept structural sharing on untouched branches. Gzip favored Immer slightly (5.3 KB vs 6.5 KB).
Short answer: pick Mutative when produce cost shows up in profiles. Pick Immer when you want the smaller gzip and the wider ecosystem. 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 00:56–00:57 IST.
- Versions: immer 11.1.21, mutative 1.3.0, Node 22.20.0, esbuild 0.28.2 minify + gzip -9.
- Fixture: /workspace/bench-immer-mutative/. State shape: meta object + 1,000 users (id, name, score, tags[], nested profile). Paths: bump one score (5,000 produces × 9 rounds, median); deep path that edits profile.city, pushes a tag, and bumps meta.version. Structural-share check: untouched user refs stay identical after a sibling edit.
- Findings: bump-one median 431.081 ms vs 14.789 ms for 5,000 updates (86.2 us vs 3.0 us each; about 11.6k vs 338k ops/s). Deep path 505.435 ms vs 43.347 ms (101 us vs 8.7 us). Gzip 5,313 B vs 6,512 B. Structural share true for both.
- One surprise: Mutative was about 29× faster on the single-field bump, yet the Immer build was about 1.2 KB smaller gzip. Speed and size do not point the same way.
- Not tested: enableMapSet / Map patches, Redux Toolkit integration, patches/inversePatches, freeze options, React bindings, real browsers, large array replace patterns.
How wide was the produce gap?
| Path (5,000 updates, median of 9) | Immer | Mutative |
|---|---|---|
| Bump one score (us per update) | 86.2 | 3.0 |
| Bump one score (ops/s) | 11,599 | 338,079 |
| Deep edit (us per update) | 101.1 | 8.7 |
| Deep edit (ops/s) | 9,892 | 115,347 |
Mutative won the wall-clock race on both paths. Structural sharing matched: after editing users[0], users[1] kept the same object reference in both libraries. Compiler-side cost notes sit next to SWC vs esbuild/Babel when the transform pipeline is the bottleneck instead.
Does the bundle size change the call?
| Browser build (esbuild minify) | Minified | gzip -9 |
|---|---|---|
| immer (produce, enableMapSet) | 13,964 B | 5,313 B |
| mutative (create, rawReturn) | 19,615 B | 6,512 B |
Immer was smaller on this import set. Disk trees were about 1.1 MB for immer against 908 KB for mutative. Track shipped weight with size-limit vs bundlesize. Schema/validation choices next to reducers: Zod vs Valibot.
Which one should you pick?
| Situation | My pick |
|---|---|
| Hot produce loop in a profile | Mutative |
| Smallest gzip for produce+Map helpers | Immer |
| Already on Redux Toolkit / Immer docs | Immer |
| New greenfield immutable updates | Mutative |
| Bundle budget under about 6 KB gzip | Immer |
Bottom line: Mutative 1.3.0 took about 3 us per bump against Immer 11.1.21 at about 86 us on a 1,000-user tree, while Immer shipped 5.3 KB gzip against Mutative at 6.5 KB. Who should not switch: apps whose Immer produces are rare and already under budget — the API is familiar and the gzip win is real. Lint after a swap with Biome vs ESLint/Prettier. Dead-code check: knip vs depcheck. Test runner: Jest vs Vitest.
How this was made: I installed Immer and Mutative on the ShopperCove box, timed produce/create loops on a 1,000-user fixture, measured esbuild minify+gzip sizes, verified structural sharing, and recorded the JSON. The write-up was drafted with AI help and checked against that output.
Sources
- https://immerjs.github.io/immer/
- https://www.npmjs.com/package/immer
- https://mutative.js.org/
- https://www.npmjs.com/package/mutative
Related
- https://www.shoppercove.com/blog/size-limit-vs-bundlesize
- https://www.shoppercove.com/blog/zod-vs-valibot
- https://www.shoppercove.com/blog/swc-vs-esbuild-babel
- https://www.shoppercove.com/blog/knip-vs-depcheck
- https://www.shoppercove.com/blog/biome-vs-eslint-prettier
- https://www.shoppercove.com/blog/jest-vs-vitest
- https://www.shoppercove.com/blog/oxlint-vs-eslint
- https://www.shoppercove.com/blog/dprint-vs-prettier
Lab evidence
What I found running this
Hands-on on ShopperCove box 6 Oct 2026 ~00:56-00:57 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 22.20.0). immer 11.1.21 vs mutative 1.3.0, esbuild 0.28.2. Fixture /workspace/bench-immer-mutative/: meta + 1,000 users (score, tags[], nested profile). Bump-one: 5,000 produce/create x 9 rounds after warm. Deep: edit profile.city, push tag, bump meta.version. Structural-share check on untouched user refs. Medians: bump 86.2 vs 3.0 us/update; deep 101.1 vs 8.7 us; gzip 5,313 vs 6,512 B. Disk ~1.1 MB vs ~908 KB. Raw: res-bench.json, res-bundle.json. Not tested: Map/Set patches, RTK wiring, patches/inversePatches, freeze options, React bindings, real browsers. No affiliate.
Related links
Plate 38
TanStack Query vs SWR: 10.4 KB vs 5.6 KB Gzip
5 Oct 2026
Plate 48
Zod vs Valibot: 1.14M vs 0.97M Parses
zod 4.6.5 vs valibot 1.5.0 on one nested user schema (uuid/email/age/tags/profile/enum). Warm medians after 2k warmup + 9×50k: zod.parse 1,144,494 ops/s vs valibot.parse 968,187 (~1.18×). safeParse fail: valibot 697,760 vs zod 415,523 (~1.7×). esbuild minify+gzip9: 452,999/92,636 B vs 85,126/15,422 B. Package trees ~6.1 MB vs ~1.9 MB. Fixture /workspace/bench-zod-valibot/.
5 Oct 2026
Plate 66
Jotai vs Zustand: 0.15 ms vs 0.17 ms per Update
5 Oct 2026