You write const next = { ...state } every day. A Redux reducer. A useState update. You read that line as “make a copy of state so I can mutate it freely” — and most of the time, that read is wrong. Below is a workbench that runs the line for you and shows what actually landed in memory. Your first job: find the gap between the story and the runtime.

Over the next seven screens you'll invent deepClone(x) — one wall at a time. Each screen hands you a tool you don't have yet and a job that tool can't do. What you invent to get past the wall turns out to be a line of the real function. We won't name the real function until the end.

The shared object
mode shallow
Runtime mirror · livetop layer only
1
const a = { user: { name: 'old-name', theme: 'dark' } };
2
// TOP-LEVEL copy: b gets a new outer container — but b.user === a.user (same inner!)
3
const b = { ...a };
4
5
// (press Rename to set b.user.name = 'Vij')

Two variables, a and b, each carrying an object with a nested user. Switch to “shallow” ({ ...a }) and press Rename. Both displayed names flip to “Vij” at once. That's because a.user and b.user are not two objects — they're one object with two names pointing at it. Now switch to “deep” (a placeholder — the next phase will unpack what deep(a) actually does) and press Rename again. Only b's name moves. That second behaviour is what you thought you were getting. Today's language gives you the first one and doesn't tell you.

Spread duplicates the outer container, not the inner objects. b is a new outer object. b.user is the same inner object a.user points to. Write through either name and the mutation lands on the single underlying piece of memory. React, Redux, every === check — they all ask “is this reference the same?” and receive “yes.” The component doesn't re-render when the displayed name moved, because from the runtime's seat, nothing about state.user changed: it's still the same object.

Try BOTH modes — flip the switch, press Rename in each. Shallow first, then deep, so the difference lands.