mrkeyoor.com_
Sun 20 Sept 07:00 UTC
npmUtilsupdated 20 Sept 2026

immutable review

Immutable.js 5.1.9 provides persistent List, Map, Set, OrderedMap, Stack, Record, Range, and lazy Seq collections. Each edit returns a new value while unchanged branches share memory, and collections compare by contents through `equals()` or `is()`. That model helps history, snapshots, and memoized selectors, but it introduces collection methods and conversion boundaries that native arrays and objects do not have. Version 5.1.9 fixes a concrete List corruption bug: an `undefined` entry could become `null` after the list grew beyond 32 elements. Our full browser import measured 66.8 KB minified and 18.9 KB gzipped.

29.5Mdownloads / wk
Verdict

Immutable 5.1.9 installed as one 1 MB package in 0.6 seconds in our sandbox, but a full import added 66.8 KB minified and 18.9 KB gzipped. Install it for persistent collection semantics you will use throughout a subsystem; for ordinary UI state, Immer or native updates keep boundaries easier to read.

We installed it

Lab card: what happened when we installed immutableScreenshot of immutable documentation
Install✓ · 0.6s1 package on disk · 1 MB
ImportESM import works · require() works · CommonJS package
Browser18.9 KBgzipped (66.8 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does immutable install cleanly?

Yes. In a fresh container with an empty cache, npm install immutable finished in 0.6s, leaving 1 package and 1 MB on disk. npm audit reported no known vulnerabilities.

How much does immutable add to a browser bundle?

18.9 KB gzipped (66.8 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does immutable work with both ESM and CommonJS?

Yes. Both import 'immutable' and require('immutable') worked in Node 22 in our run. The package is published as CommonJS.

Does immutable include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

immutable or immer: which should you use?

immer: Use it when immutable updates should preserve ordinary JavaScript objects and arrays. Immutable 5.1.9 installed as one 1 MB package in 0.6 seconds in our sandbox, but a full import added 66.8 KB minified and 18.9 KB gzipped.

When should you not use immutable?

Most application state is small JSON-shaped data. Immer keeps native objects and arrays, avoiding Immutable conversion at component and API edges.

API stability4/5List, Map, Set, Seq, Record, nested-path operations, and content equality have kept recognizable roles across major releases. Version 5 still asks migrations to replace the default export, adjust Range calls, remove deprecated statics, and address stricter TypeScript Map inference. The 5.1.9 patch also shows that collection edge cases around trie growth can affect stored values.
Docs5/5immutable-js.com documents persistence, structural sharing, equality costs, no-op reference reuse, nested updates, conversion depth, mutation batching, lazy Seq behavior, TypeScript requirements, and every collection method. The README calls out the string-key behavior of JavaScript objects and the fact that Seq does not cache, two details many quick examples leave out.
Maintenance5/5The repository is unarchived, was pushed on 2026-08-17, and GitHub reports 128 open issues and pull requests. Release 5.1.9 arrived on 2026-06-29 to preserve `undefined` values when a List grows past 32 entries. Recent releases also address large List bounds and Map collision behavior, while a 6.0 beta keeps future work visible.
Ecosystem4/5npm recorded 43,838,631 downloads in the latest completed week and GitHub reports 33,040 stars. The package ships TypeScript declarations and works through CommonJS and ESM consumers despite lacking an exports map. Redux and older Flux projects know its data model, but current React libraries more often expect plain objects and arrays at their public boundaries.

Use it if

  • An editor or state engine retains many versions of large collections and deep copying has become measurable.
  • Maps or Sets need collection values as keys with content-based equality and hashes.
  • Selectors can exploit the original reference returned by an update that changes no value.
  • A long or infinite Range should pass through lazy map, filter, take, and materialization steps.
Skip it if

Setup reality

We installed immutable 5.1.9 in a fresh Node 22 Bookworm sandbox in 0.6 seconds. It left one package and 1 MB on disk. The package has 0 direct dependencies, 0 peer dependencies, and a 732 KB unpacked size; npm audit found 0 vulnerabilities. It includes TypeScript declarations. Although its metadata describes a CommonJS package without an exports map, both require() and ESM import worked in our checks. A full esbuild import produced 66.8 KB minified and 18.9 KB gzipped.

No credential, native compiler, or config file is involved. Type declarations need ES2015 library types for iterators. Version 5 uses named imports such as import { Map } from 'immutable'; the previous default export is gone. More precise Map inference can expose v4 code that mixed object keys and values. An endless Range now has to spell out both bounds, for example Range(0, Infinity).

Set conversion rules before adopting the library across a codebase. fromJS() recursively replaces objects and arrays with Map and List. toJS() recursively creates fresh plain data, so using it inside React renders or cached selectors breaks the reference checks you adopted Immutable for. toObject() and toArray() are shallow. Object keys were strings before conversion, which means fromJS({1: 'x'}).get(1) does not find the stored key.

Seq evaluates lazily and keeps no result cache. Two iterations perform the pipeline twice; call toList() when the result will be reused. withMutations() speeds a supported group of writes through a temporary mutable view, but ignoring a method that returns a separate collection loses that update. Use equals() or is() for content equality. A matching === proves no value changed; two different references may still contain equal data.

Patterns

Change one Map value update-map

import { Map } from 'immutable';

const before = Map({ status: 'draft', count: 1 });
const after = before.set('status', 'ready');

console.log(before.get('status'));
console.log(after.get('status'));

When `set()` receives the value already stored at that key, it returns the original Map reference.

Import version 5 collections import-v5

import { List, Map, Record } from 'immutable';

const ids = List([1, 2]);
const flags = Map({ active: true });

Version 5 has no default export. Named imports are the direct migration target; a namespace import can preserve `Immutable.Map` call sites temporarily.

Test two collections by contents compare-values

import { Map, is } from 'immutable';

const left = Map({ id: 7 });
const right = Map({ id: 7 });

console.log(left === right);
console.log(left.equals(right));
console.log(is(left, right));

`===` checks identity. Use `equals()` or `is()` when two separately constructed collections should compare by their values.

Convert a nested payload once convert-nested-data

import { fromJS } from 'immutable';

const state = fromJS({
  users: { u1: { tags: ['admin'] } },
});

const next = state.updateIn(
  ['users', 'u1', 'tags'],
  (tags) => tags.push('owner'),
);

`fromJS()` converts the nested tags array into a List, so later writes must use List methods such as `push()`.

Read a nested path with a fallback read-nested-value

const theme = state.getIn(
  ['preferences', 'theme'],
  'system',
);

The fallback applies to a missing path. If the path exists and stores `undefined`, the result remains `undefined`.

Group supported List writes batch-updates

import { List } from 'immutable';

const before = List([1, 2, 3]);
const after = before.withMutations((draft) => {
  draft.push(4);
  draft.set(0, 10);
  draft.pop();
});

Only methods documented for `withMutations()` are safe here. Keep the returned collection from any operation that produces a separate value.

Create a fixed Record factory define-record

import { Record } from 'immutable';

const Settings = Record({ theme: 'light', fontSize: 14 });
const first = Settings();
const second = first.set('theme', 'dark');

console.log(second.theme);

A Record accepts only its declared keys and supplies defaults. Give TypeScript an explicit instance type when property access must be checked.

Merge inside nested collections merge-nested

import { fromJS } from 'immutable';

const base = fromJS({ prefs: { theme: 'light', size: 14 } });
const next = base.mergeDeep({ prefs: { size: 16 } });

console.log(next.getIn(['prefs', 'theme']));

`merge()` replaces at its current level; `mergeDeep()` descends into compatible nested collections.

Bound an infinite Range build-lazy-sequence

import { Range } from 'immutable';

const values = Range(0, Infinity)
  .filter((value) => value % 2 === 0)
  .map((value) => value * value)
  .take(5)
  .toList();

Version 5 expects start and end arguments. Apply `take()` or another finite operation before realizing `Infinity` into a strict collection.

Materialize a reused Seq pipeline materialize-seq

import { Seq } from 'immutable';

const pipeline = Seq(source)
  .filter(isEligible)
  .map(toSummary);

const summaries = pipeline.toList();
renderList(summaries);
writeReport(summaries);

Seq does not cache evaluated items. `toList()` performs the pipeline once so both consumers reuse the same result.

Convert only the public subtree serialize-boundary

const responseBody = state.get('publicData').toJS();
res.json(responseBody);

`toJS()` copies recursively. Selecting the response subtree first prevents an API handler from copying unrelated application state.

Remember partition result order partition-list

import { List } from 'immutable';

const numbers = List([1, 2, 3, 4, 5, 6]);
const [odd, even] = numbers.partition((value) => value % 2 === 0);

`partition()` returns rejected values first and accepted values second. Name the tuple entries so this ordering is visible in code review.

Alternatives

PackageRegistryPick it when
immernpmUse it when immutable updates should preserve ordinary JavaScript objects and arrays.
mutativenpmUse it for a draft-style update API after benchmarking the hot state paths that matter.
@reduxjs/toolkitnpmUse it when Redux store structure, reducers, and async flows are the actual problem to solve.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.