Immutability
Immutability is an approach in which data is not changed in place; instead, an updated copy is created. This makes bugs easier to track down: an object cannot suddenly change because of code at the other end of the program. React and many other libraries are built on this approach.
Primitives do not change
Strings, numbers and other primitives are immutable. String methods do not touch the original string; they return a new one:
const word = 'cat';
const upper = word.toUpperCase();
console.log(word); // cat
console.log(upper); // CATObjects are passed by reference
Objects and arrays are different. A variable holds not the object itself but a reference to it, so two variables can point to the same object:
const anna = { score: 1 };
const player = anna;
player.score = 2;
console.log(anna.score); // 2const does not help here: it forbids assigning another value to the variable, but it does not stop you from changing the object's contents.
Updating through a copy
Instead of changing an object, you create a new one with the edits. The spread operator ... copies the old properties, and the new ones follow:
const user = { name: 'Anna', age: 28 };
const older = { ...user, age: 29 };
console.log(user.age); // 28
console.log(older.age); // 29The same goes for arrays: [...list, item] instead of list.push(item).
Array methods without mutation
Some array methods change the original array. They have counterparts that return a copy:
| Changes the array | Returns a copy |
|---|---|
sort() | toSorted() |
reverse() | toReversed() |
splice() | toSpliced() |
arr[i] = x | arr.with(i, x) |
const numbers = [3, 1, 2];
const sorted = numbers.toSorted();
console.log(numbers.join(', ')); // 3, 1, 2
console.log(sorted.join(', ')); // 1, 2, 3Object.freeze
Object.freeze() "freezes" an object: its properties can no longer be changed, added or removed. An attempt is silently ignored, and in strict mode it throws an error:
const config = Object.freeze({ currency: '€' });
config.currency = '$';
console.log(config.currency); // €Freezing is shallow
Object.freeze() works only on the top level: nested objects and arrays stay mutable. A complete independent copy with all nested data is made by the structuredClone() function:
const order = { items: ['tea'] };
const copy = structuredClone(order);
copy.items.push('coffee');
console.log(order.items.length); // 1
console.log(copy.items.length); // 2Spread also copies only the top level: in a { ...order } copy the items array would be the very same one.
Common mistake
Thinking that sort() returns a sorted copy. It sorts the array itself, and the original order is lost:
const prices = [30, 10, 20];
const sorted = prices.sort((a, b) => a - b);
console.log(prices.join(', ')); // 10, 20, 30
console.log(sorted === prices); // trueIf you still need the original array, use toSorted() or sort a copy: [...prices].sort().