Suggest an editImprove this articleRefine the answer for “What is immutability?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Immutability** - instead of modifying an existing value, you create a new one with your changes. ```javascript const user = { name: "Alice", age: 25 }; const updated = { ...user, age: 26 }; // user is unchanged; updated is a new object in memory ``` **Key point:** React and Redux compare object references, not their contents. Mutating the original in place breaks their change detection.Shown above the full answer for quick recall.Answer (EN)Image**Immutability** - once you create a value, you don't modify it in place. You create a new value with your desired changes, leaving the original untouched. ## Theory ### TL;DR - Like a photo: you can't edit the original, but you can take a new shot with different settings - Mutating changes an existing object at the same memory address; immutability allocates a new one - React and Redux compare object references to detect changes, not their contents, so immutability is not optional there - Use it when multiple parts of your code share the same object, or when you need predictable state transitions ### Quick example ```javascript // Mutation - changes the original const user = { name: "Alice", age: 25 }; user.age = 26; console.log(user); // { name: "Alice", age: 26 } - original modified // Immutability - creates a new object const user2 = { name: "Alice", age: 25 }; const updatedUser = { ...user2, age: 26 }; console.log(user2); // { name: "Alice", age: 25 } - unchanged console.log(updatedUser); // { name: "Alice", age: 26 } - new object ``` The spread operator creates a new object in memory. The original stays at its old memory address, untouched. ### Key difference When you mutate, you're modifying data at an existing memory address. Any other variable pointing to that address sees the change too. With immutability, you leave the original alone and allocate new memory for the updated version. Code that holds a reference to the old object keeps seeing the old data, which is exactly what you want when tracking state transitions. ### When to use - React state: `useState` expects a new object reference to trigger a re-render; mutating and passing the same reference does nothing - Redux reducers: always return a new state object, never modify the previous one - Shared data: when two or more functions reference the same object, mutations create bugs that are hard to trace - Function parameters: returning a new array instead of mutating the input keeps side effects out of the caller's scope - Async code: when multiple operations access the same data, immutable values prevent race conditions ### Common mistakes **Mistake 1: Assignment is not a copy** ```javascript const original = { count: 0 }; const copy = original; // same reference, not a copy copy.count = 1; console.log(original.count); // 1 - both point to the same object // Right const safeCopy = { ...original }; safeCopy.count = 1; console.log(original.count); // 0 - original untouched ``` **Mistake 2: Shallow copy doesn't protect nested objects** ```javascript const user = { name: "Alice", settings: { theme: "dark" } }; const copy = { ...user }; copy.settings.theme = "light"; console.log(user.settings.theme); // "light" - nested object was mutated // Right: spread both levels const safeCopy = { ...user, settings: { ...user.settings, theme: "light" } }; console.log(user.settings.theme); // "dark" - protected ``` Shallow copy duplicates only the top-level properties; nested objects still share the same reference. I've seen this catch developers who were sure they were writing immutable code. **Mistake 3: Some array methods mutate** ```javascript // These mutate the original nums.push(4); nums.splice(0, 1); nums.sort(); // These return new arrays const added = [...nums, 4]; const removed = nums.slice(1); const sorted = [...nums].sort(); ``` **Mistake 4: Mutating state in React** ```javascript const [items, setItems] = useState([1, 2, 3]); // Wrong - React sees the same reference, skips re-render items.push(4); setItems(items); // Right - new reference triggers re-render setItems([...items, 4]); ``` ### Real-world usage - React: props and state are treated as immutable; `useState` expects new references to detect changes - Redux: reducers return new state objects on every action - Immer.js: write mutation-style code that produces immutable updates internally - JavaScript itself: `toSorted()`, `toReversed()`, `toSpliced()`, and `with()` are newer array methods that return new arrays instead of mutating ### Follow-up questions **Q:** Is `const` the same as immutability? **A:** No. `const` prevents reassignment of a variable, but the object can still be mutated. `const obj = {}; obj.prop = "value"` is valid. Immutability is about the object's contents, not the variable binding. **Q:** What is the performance cost of creating new objects everywhere? **A:** Modern JavaScript engines handle it well. For large or deeply nested data, libraries like Immer use structural sharing - only the changed path is copied, everything else reuses the same references. Memory usage stays manageable. **Q:** How do you handle deeply nested updates without verbose spreading? **A:** Immer.js. You write `produce(state, draft => { draft.user.address.city = "LA"; })` and get a new immutable state without chaining spread operators at every level. **Q:** (Senior) What is structural sharing and how does Immer use it? **A:** Structural sharing means unchanged branches of a data tree are reused between versions. When you update one property, only the objects along that path get copied; everything else is the same reference as before. This is how Immer and Immutable.js keep updates efficient at O(log n) instead of O(n). ## Examples ### Intermediate: Immutable state updates in React ```javascript function UserProfile() { const [user, setUser] = useState({ name: "Alice", address: { city: "NYC", zip: "10001" } }); const updateCity = (newCity) => { setUser({ ...user, address: { ...user.address, city: newCity // only this field changes } }); }; return <button onClick={() => updateCity("LA")}>Move to LA</button>; } ``` Updating a nested field requires spreading both the outer object and the nested one. Skip either and you produce a mutation React won't detect. ### Advanced: The array reference trap ```javascript const items = [1, 2, 3]; const newItems = items; // reference, not a copy newItems.push(4); console.log(items); // [1, 2, 3, 4] - both changed console.log(items === newItems); // true - same object in memory // Shallow copy is better, but not perfect for nested structures const copy = items.slice(); copy.push(4); console.log(items); // [1, 2, 3] - safe at the top level // Nested arrays still share references const matrix = [[1, 2], [3, 4]]; const shallowCopy = matrix.slice(); shallowCopy[0][0] = 99; console.log(matrix); // [[99, 2], [3, 4]] - inner array was mutated // structuredClone solves this const deepCopy = structuredClone(matrix); deepCopy[0][0] = 99; console.log(matrix); // [[1, 2], [3, 4]] - original safe ``` `structuredClone()` is available in modern browsers and Node 17+. For older environments, Immer or Lodash's `cloneDeep` cover the same ground.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.