← All concepts
Modern JavaScript

Immutable Update Patterns

To change one nested value without mutating anything, you copy every level on the path down to it, and reuse everything else as-is.

Definition

An immutable update replaces a piece of nested state with a new value without ever mutating the original object or array in place. Because objects and arrays are stored by reference, you cannot just reach into a nested field and assign to it; that would mutate the original object, and anything else still holding a reference to it (another variable, a previous render, an undo history) would see the change too. Instead, you build a new outer object whose properties are copies of the old ones, except the one property on the path to the change, which itself becomes a new object built the same way, recursively, until you reach the value you actually want to change. The key insight is that only the objects on the path from the root to the changed value need to be copied. A sibling branch that the update never touches, an object hanging off a property you did not change, can be reused exactly as it was; the new parent object simply keeps the same reference to it. This is called structural sharing: the new and old trees share every part they did not change, and only the path to the actual edit consists of freshly created objects. Spread syntax (...) is the common tool for building each of those shallow copies.

Examples

const state = { user: { name: "Ada", address: { city: "NYC" } }, items: [1, 2, 3] };
const next = {
  ...state,
  user: { ...state.user, address: { ...state.user.address, city: "Boston" } },
};
next.items === state.items; // true, untouched branch is reused

state, user, and address are all copied because they sit on the path to city, but items was never touched, so next.items is literally the same array reference as state.items.

// Mutating instead: breaks sharing assumptions
state.user.address.city = "Boston"; // avoid: mutates the original object in place

This changes state directly, so any other code holding a reference to the old state (or its user or address) sees the mutation too, which is exactly what immutable updates are meant to avoid.

Common mistakes

  • Spreading only the top level and forgetting nested objects still share the old reference, so a deeper mutation slips through unnoticed.
  • Mutating a nested field directly because it feels shorter, breaking any code that relies on reference equality to detect changes.
  • Assuming an immutable update requires copying the entire tree; only the objects on the path to the change need to be new.

Key takeaways

  • An immutable update copies every object on the path from the root down to the changed value.
  • Branches the update never touches keep their original reference; nothing about them needs to be copied.
  • This sharing of untouched branches between old and new state is called structural sharing.
  • Spread syntax is the common way to build each shallow copy on the path.
Check your understanding

In an immutable update to state.user.address.city, which parts of state end up as brand new objects?

Where you see this

  • Updating nested React or Redux state, where a component only re-renders if it gets a new reference for the data it reads.
  • Implementing undo history, where each past state must stay exactly as it was, unaffected by later edits.
  • Updating one item inside an array of objects by mapping to a new array with only that one item replaced.
  • Debugging why a UI does not re-render even though data changed, usually because it was mutated in place instead of replaced with a new reference.

Practice this

Further reading

Previous
ES Modules
Next
Regular Expressions