Mutative - 10x faster than Immer
2022-12-31
Mutative - A JavaScript library for efficient immutable updates. In the benchmark below, it is faster than a naive handcrafted reducer and more than 10x faster than Immer.
Motivation
Writing immutable updates by hand is usually difficult, prone to errors, and cumbersome. Immer helps us write simpler immutable updates with “mutative” logic.
However, Immer can introduce runtime overhead. In the benchmark below, Immer performs much worse when auto-freeze is disabled, while Mutative disables auto-freeze by default. In scenarios such as cross-process communication or remote data transfer, repeatedly freezing immutable data can also become expensive.
There are also other areas to improve, such as better type inference, non-intrusive marking, support for more immutable data types, safer immutable updates, and additional edge cases.
This is why Mutative was created.
Repo: https://github.com/unadlib/mutative
Mutative vs Immer Performance
Mutative passed all of Immer’s test cases.
Measured in ops/sec while updating 50K arrays and 1K objects. Higher is better (view source). [Mutative v0.5.0 vs Immer v10.0.1]

1 | Naive handcrafted reducer - No Freeze x 3,692 ops/sec ±1.28% (95 runs sampled) |
Run yarn benchmark to measure performance.
OS: macOS 12.6, CPU: Apple M1 Max, Node.js: 16.14.2
In this benchmark, Immer performs best with auto-freeze enabled. When auto-freeze is disabled for this workload, Mutative has a much larger lead, especially with large data structures.
Overall, Mutative has a huge performance lead over Immer in more performance testing scenarios. Run yarn performance to get all the performance results locally.
Features and Benefits
- Mutation makes immutable updates - Immutable data structures for objects, arrays, Sets, and Maps.
- High performance - More than 10x faster than Immer by default in the benchmark above, and even faster than a naive handcrafted reducer.
- Optional state freezing - Immutable data is not frozen by default.
- JSON Patch-compatible patches - Can emit RFC 6902-compatible patches when configured.
- Custom shallow copy - Supports more immutable data types.
- Immutable and mutable data markers - Allows non-invasive marking.
- Safer mutable data access in strict mode - Makes immutable updates safer.
- Reducer support - Works with reducer functions and other immutable state libraries.
Difference between Mutative and Immer
| Mutative | Immer | |
|---|---|---|
| Custom shallow copy | ✅ | ❌ |
| Strict mode | ✅ | ❌ |
| No data freeze by default | ✅ | ❌ |
| Non-invasive marking | ✅ | ❌ |
| Complete freeze data | ✅ | ❌ |
| Non-global config | ✅ | ❌ |
| async draft function | ✅ | ❌ |
| RFC 6902-compatible patch output (configurable) | ✅ | ❌ |
Mutative has fewer bugs such as accidental draft escapes than Immer, view details.
Installation
1 | yarn install mutative # npm install mutative |
Usage
Use create() for draft mutation to get a new state, which also supports currying.
1 | import { create } from 'mutative'; |
In this basic example, the changes to the draft are ‘mutative’ within the draft callback, and create() is finally executed with a new immutable state.
create(state, fn, options) - The options argument is optional.
strict -
boolean, the default is false.Forbid accessing non-draftable values in strict mode (unless using unsafe()).
It is recommended to enable
strictin development mode and disablestrictin production mode. This keeps explicit returns safer during development while preserving production performance. If you need to return a value that does not contain any current draft, or if you need to returnundefined, use rawReturn().enablePatches -
boolean | { pathAsArray?: boolean; arrayLengthAssignment?: boolean; }, the default is false.Enable patches and return
patches/inversePatches.To control the generated patch shape, configure
pathAsArrayandarrayLengthAssignment.pathAsArraydefaults totrue; when enabled, paths are arrays. Set it tofalseto emit JSON Pointer strings.arrayLengthAssignmentdefaults totrue; when enabled, array length changes are included in patches. For RFC 6902-compatible JSON Patch output, use JSON Pointer paths and disable array length assignment. This may have additional performance cost. View related discussions.enableAutoFreeze -
boolean, the default is false.Enable
autoFreeze, return a frozen state, and enable circular reference checking only indevelopmentmode.mark -
(target) => ('mutable'|'immutable'|function)Set a marker to determine whether a value is mutable or immutable. It can also return a shallow copy function.
autoFreezeand patches should both be disabled when using this option.
FAQs
- Why does Mutative have such good performance?
Mutative focuses its optimizations on shallow copies, lazy drafts, the finalization process, and related internal paths.
- I’m already using Immer, can I migrate smoothly to Mutative?
Yes. Unless you must support Internet Explorer, Mutative supports almost all Immer features, and you can usually migrate from Immer to Mutative smoothly.
Migration is also not possible in React Native environments that do not support Proxy.
Conclusion
Mutative is inspired by Immer.
Mutative focuses on efficient immutable updates, performance improvements, and better APIs for a stronger development experience. If you think Mutative is useful, feel free to give it a star.