Immutability means a value cannot change after it is created. Not "should not" — cannot. Any operation that looks like an update produces a new value and leaves the original exactly as it was. That guarantee removes an entire category of bugs where something changes data out from under code that was not expecting it, which is why immutability shows up so often in state management, concurrent code, and functional-style programming.
What changed in 2026
- Immutable-by-default became the norm in more new language features. Recent additions across mainstream languages increasingly favor immutable records and read-only bindings as the first option, with mutable variants requiring explicit opt-in — check the current defaults for your language and version.
- Structural sharing libraries matured further. Persistent data structure libraries that avoid full copies on every update are more battle-tested and see wider adoption in performance-sensitive code.
- State-management tooling leans on immutability more strictly. More frameworks detect and warn about accidental in-place mutation of state during development, rather than letting it fail silently in production.
Immutable vs mutable, precisely
A mutable value can be changed in place: push an item onto an array, and the same array object now has different contents. An immutable value is never changed in place: "pushing" an item onto an immutable list returns a new list with the item added, and the original list still has exactly what it had before.
// Mutable
const list = [1, 2, 3];
list.push(4); // same array, now [1, 2, 3, 4]
// Immutable
const list2 = [1, 2, 3];
const list3 = [...list2, 4]; // new array; list2 is still [1, 2, 3]
Const is not immutability
This is the most common confusion. const in JavaScript, and similar keywords elsewhere, only stops a variable from being reassigned. It says nothing about whether the value it points to can be changed internally.
const user = Object.freeze({ name: "Ada", address: { city: "London" } });
user.name = "Grace"; // blocked — freeze covers this top level
user.address.city = "Paris"; // this works — freeze is shallow only
Object.freeze only locks the top level of an object. Nested objects inside remain fully mutable unless frozen too, recursively. This is one of the most common real-world immutability bugs: code assuming a frozen object is safe everywhere, when only its first layer is.
How common languages approach it
| Language / tool |
Immutable by default? |
How you opt in |
| JavaScript |
No |
Object.freeze (shallow), immutable libraries |
| Python |
No, except tuples and frozensets |
frozen=True dataclasses, tuples |
| Java |
No |
final fields, records (shallow immutability) |
| Rust |
Yes |
mut keyword required to opt into mutability |
| Clojure |
Yes |
Core data structures are persistent by default |
Some languages make mutation the thing you opt into; others make immutability the default. Neither is objectively correct, but it changes which kind of bug is more likely by default.
Why structural sharing makes it affordable
Naively, "return a new value on every update" sounds slow — copy the whole list every time you add one item. Persistent data structures avoid this with structural sharing: the new version reuses every unchanged part of the old version internally (typically via tree-like structures) and only allocates the piece that actually changed. The result is close to constant-time updates, not a full copy.
Where immutability earns its keep
- Concurrency. Data that cannot change cannot cause a race condition. Sharing immutable data across threads needs no locks.
- State management. Detecting "did anything change" becomes a cheap reference comparison instead of a deep one, which is how many UI frameworks decide what to re-render.
- Debugging and undo/redo. If old values are never destroyed, keeping history is just keeping references, not deep-copying state at every step.
Common mistakes
Assuming a frozen object is fully protected. Shallow freezing is the default in most languages that offer it. Nested structures need their own protection.
Manually deep-cloning on every update as a substitute for real immutability. It works, but is often far more expensive than structural sharing, and easy to get subtly wrong with cyclic references or special object types.
Mixing mutable and immutable conventions in the same module. Half the functions returning new objects and half mutating in place is a reliable source of confusing bugs. Pick one convention and stay consistent.
FAQ
Does immutability make code slower?
It can, if implemented naively with full copies. With structural sharing or a well-built library, the overhead is usually small and worth the predictability gained.
Is a frozen array actually immutable in JavaScript?
Its own elements cannot be reassigned, but if an element is itself an object, that nested object remains mutable unless frozen separately.
Do functional languages enforce immutability, or just encourage it?
It varies. Haskell and Clojure make immutability the default and mutation the exception. Others, including functional-style JavaScript or Python, only encourage it through convention and libraries.
Is immutability only relevant to functional programming?
No. Object-oriented codebases benefit from immutable value objects too — anywhere shared state causes bugs, immutability is a reasonable tool regardless of paradigm.
Where to go next