Understanding Nested Object Updates in JavaScript

Docs team
11 Jan 2022
5 min read

For many developers, updating deeply nested objects in JavaScript can feel like walking through a maze—especially when trying to do it immutably. One particularly elegant pattern sheds light on how to approach this problem:

const newObj = { ...obj };
let current = newObj;

for (const key of keys) {
 current[key] = { ...current[key] }; // shallow clone each level
 current = current[key];            // step into the cloned level
}

current[lastKey] = value;
return newObj;

At first glance, this might appear to be a simple series of shallow clones. But the key insight lies in a deceptively simple line:

current = current[key];

This line is where the magic happens—current is reassigned to point to its freshly cloned child.

The Common Confusion

Many developers find this step confusing. It seems as though the code is mutating an object and then immediately diving into the thing it just mutated. There’s a lingering question: is this circular logic?

The answer is no. It’s actually a carefully structured technique that allows only the necessary path to be cloned, leaving the rest of the object untouched and shared by reference.

Why This Pattern Works (And What Fails Without It)

An alternate approach some try is:

let temp = { ...current[key] };
current = temp;

This might seem equivalent, but it fails for one crucial reason: the newly cloned temp isn’t connected back to the parent object. Without assigning it to current[key], it floats in isolation and never becomes part of the updated structure.

In contrast, the correct version:

  1. Clones and assigns the child back to the current level (current[key] = { ...current[key] }).
  2. Then steps into that child (current = current[key]).

This ensures that the clone is correctly inserted into the object tree at every step of the path.

A Helpful Analogy: Russian Dolls

Imagine a nested object structure like a set of Russian dolls or deeply nested folders:

  • At each level, a new copy of the current "doll" is made.
  • The process then steps into the freshly cloned level.
  • This repeats until reaching the key where the update happens.

Only the layers along the path are cloned. Everything else remains unchanged and continues to share the original references.

Why It Matters

This pattern is fundamental in state management systems like Redux, where immutable updates are essential. It allows developers to:

  • Update only the necessary parts of the state.
  • Preserve reference equality for unchanged branches of the object tree.

It’s more than just a clever trick—it’s a practical mental model for working with complex data structures.

A Developer's Superpower

Once this concept clicks, it unlocks a new level of fluency in JavaScript. Whether building form editors, config generators, or maintaining immutability in application state, this pattern becomes a vital tool.

Pro Tip

For repeated use, this logic can be abstracted into a utility function like:

function setIn(obj, pathArray, value) { ... }

Or developers can rely on libraries such as lodash/fp or immer to simplify immutable data handling.

Understanding how to update nested objects immutably is a turning point in mastering modern JavaScript. Once internalized, it transforms how developers think about and manipulate state.

Docs team
11 Jan 2022
5 min read