Immutability prevents bugs, simplifies debugging, and enables powerful optimizations like React's reconciliation. Here's how to do it right.
Why Immutability?
// Mutable — bug-prone
const user = { name: "Rahul", scores: [90, 85] };
const copy = user;
copy.name = "Changed"; // Oops — user.name is also "Changed"!
// Immutable — safe
const user = { name: "Rahul", scores: [90, 85] };
const copy = { ...user, name: "Changed" }; // Original unchangedShallow vs Deep Copy
const original = { a: 1, nested: { b: 2 } };
// Shallow copy — nested objects are still shared!
const shallow = { ...original };
shallow.nested.b = 99; // original.nested.b is also 99!
// Deep copy
const deep = structuredClone(original); // Modern way
deep.nested.b = 99; // original.nested.b is still 2Object.freeze: Limited Protection
const config = Object.freeze({
api: "https://api.example.com",
nested: { timeout: 5000 }
});
config.api = "changed"; // Silently fails (or throws in strict mode)
config.nested.timeout = 999; // WORKS! freeze is shallow!
// Deep freeze
function deepFreeze(obj) {
Object.freeze(obj);
Object.values(obj).forEach(v => {
if (typeof v === "object" && v !== null) deepFreeze(v);
});
return obj;
}Immutable Update Patterns
// Object
const updated = { ...state, name: "New Name" };
// Nested object
const updated = {
...state,
address: { ...state.address, city: "Mumbai" }
};
// Array: add
const added = [...items, newItem];
// Array: remove
const removed = items.filter(i => i.id !== targetId);
// Array: update
const updated = items.map(i => i.id === targetId ? { ...i, done: true } : i);Immer: Immutability Made Easy
import { produce } from "immer";
const nextState = produce(state, draft => {
// Mutate the draft — Immer produces an immutable result
draft.users[0].name = "Updated";
draft.users.push({ name: "New User" });
draft.settings.theme = "dark";
});
// state is unchanged, nextState has the updatesPerformance Considerations
- Spreading large objects is O(n) — avoid in hot loops
structuredCloneis slow for large objects — only deep clone when necessary- Immer uses structural sharing — only changed paths create new references
- React uses reference equality for re-render decisions — immutability enables this optimization