Immutability
Immutability means that once a value is created, it cannot be changed. Instead of modifying existing data, you create new data with the changes applied. This prevents a class of bugs caused by unexpected mutations.
Explanation
Primitive values in JavaScript (numbers, strings, booleans, null, undefined) are inherently immutable — you can't change the value 42, you can only create a new value. Objects and arrays are mutable by default — you can add, remove, or modify properties and elements. The pitfall: since objects are passed by reference, one function modifying an object can silently affect other code that holds a reference to the same object. Immutable programming means treating objects and arrays as if they're immutable: instead of modifying them, create new copies with the desired changes. JavaScript's spread operator and Object.assign() enable this: const newUser = {...user, age: 31} creates a new object with age changed, leaving the original user unchanged. For arrays: const newItems = [...items, newItem] adds an item immutably; items.filter(i => i.id !== id) removes immutably. React's entire state model is built on immutability: setState with the same object reference doesn't trigger re-renders; React detects state changes by reference comparison. If you mutate state directly (state.count++ instead of setState({...state, count: state.count + 1})), React doesn't detect the change and the UI doesn't update. This is one of the most common React bugs. Redux enforces immutability at the reducer level: reducers must return new state objects, never mutate the existing state. Immer.js is a popular library that lets you write "mutating" code but produces immutable updates under the hood — the mental model of mutation with the guarantees of immutability.
Code Example
javascript// Mutable vs immutable patterns in JavaScript
// MUTABLE (problematic when shared)
const user = { name: 'Alice', age: 30, hobbies: ['reading'] };
function birthday(u) { u.age++; } // mutates the original!
birthday(user);
console.log(user.age); // 31 — original is changed
// IMMUTABLE (safe to share)
function birthdayImmutable(u) {
return { ...u, age: u.age + 1 }; // new object, original untouched
}
const older = birthdayImmutable(user);
console.log(user.age); // 30 — unchanged
console.log(older.age); // 31
// Array immutability
const items = [1, 2, 3, 4, 5];
// Mutable operations (avoid when shared):
items.push(6); // mutates
items.splice(1,1); // mutates
// Immutable alternatives:
const added = [...items, 6]; // add
const removed = items.filter(x => x !== 3); // remove
const updated = items.map(x => x === 3 ? 99 : x); // update
const inserted = [...items.slice(0,2), 99, ...items.slice(2)]; // insert at index
// React: why mutation breaks state
// BAD: direct mutation — React doesn't detect change
const [user, setUser] = React.useState({ name: 'Alice', age: 30 });
user.age = 31; // mutation! setUser was never called, no re-render
// GOOD: immutable update
setUser(prev => ({ ...prev, age: prev.age + 1 })); // triggers re-render
Why It Matters for Engineers
Immutability eliminates an entire class of bugs: unexpected mutations, shared state corruption, and React/Redux state update failures. These bugs are particularly insidious because the mutation happens in one place but manifests as a UI error in a completely different component. Immutable data makes the cause-and-effect chain between state changes and UI updates explicit. Immutability also enables performance optimizations: React.memo, useMemo, and Redux's connect use reference equality (===) to determine if re-rendering is needed. With immutable updates, you can check "did this object change?" with a simple reference comparison instead of a deep equality check.
Learn This In Practice
Go deeper with the full module on Beyond Vibe Code.
Programming Foundations → →