Just JavaScript· 08
Chapter 08 · Objects, in depth

The Malibu mystery

Sherlock and John share one address object. John moves to Malibu — and somehow Sherlock ends up there too. Mutation is "spooky action at a distance": change an object, and every wire pointing at it sees the change. Here is exactly why, and two ways to fix it.

Recall the puzzle from Properties. Sherlock has a London address; John moves in pointing his address at Sherlock's; then John renames himself and moves out:

let sherlock = {
  surname: 'Holmes',
  address: { city: 'London' }
};
let john = {
  surname: 'Watson',
  address: sherlock.address
};
john.surname = 'Lennon';
john.address.city = 'Malibu';

The surprise: sherlock.address.city becomes "Malibu" too. Step the simulator below to watch the wires, then flip to either fix and compare.

Lab · spooky action, step by step

Watch the shared object get mutated — and fix it

The bug runs the code as written. Fix A reassigns john.address to a brand-new object. Fix B reassigns the whole john variable. Step each one and read the four answers update live.

Step 0 — press Step to begin
ReadyPick a version, then walk it one line at a time.
sherlock.surname
sherlock.address.city
john.surname
john.address.city
01

Objects are never nested

Two pairs of curly braces means two separate objects. One object can never be inside another — it can only be pointed at.

In the code, Sherlock's address object looks like it sits "inside" the Sherlock object. In our universe it doesn't. There are two distinct objects floating in space; the outer one has an address wire pointing at the inner one. If you still picture nesting, drop it now — it is the root of most mutation surprises.

Properties always point at values

When you write address: sherlock.address, it is tempting to read it as "point at Sherlock's address property". You can't. JavaScript first works out the value of sherlock.address — the address object — and points John's address wire at that same value. Now two address wires land on one object. That is the shared object.

02

Mutation & spooky action

Mutation just means "change an object". The danger is purely about which object — and who else is pointing at it.

Look closely at the two changes. They read alike but do very different things:

john.surname = 'Lennon';     // re-aims John's OWN surname wire
john.address.city = 'Malibu'; // mutates the SHARED address object

The first line mutates the object john points at — exactly what we intended. The second follows john.address to the shared object and mutates itscity. But sherlock.address reaches that very same object, so Sherlock is dragged to Malibu too. People say "mutate" because it carries a warning: by the time you change an object, other wires may already point at it, and your change is visible through every one of them.

Think first

In john.address.city = 'Malibu', which object is mutated — John's object, or the address object?

Answer · the address object

The wire on the left is john.address.city. We follow john to John's object, then address to the shared address object, and mutate that object's city. John's object is untouched — only its address wire was followed, not changed. Because Sherlock shares that address object, his city changes too.

Two fixes, same result

Fix A — mutate a different object. Give John a brand-new address object instead of editing the shared one:

john.surname = 'Lennon';
john.address = { city: 'Malibu' }; // new object; left wire is john.address

Now the left wire is john.address, so we re-aim John's own address wire at a fresh object. Sherlock's address object is never touched, so he stays in London.

Fix B — don't mutate at all. Reassign the whole variable to a new version of John's data:

john = {
  surname: 'Lennon',
  address: { city: 'Malibu' }
};

john now points at a different object whose address also points at a brand-new object. The old John object is abandoned; JavaScript will reclaim it automatically once no wires reach it. Both fixes give the same four answers — step Fix A and Fix B in the lab to see their diagrams differ.

Visually similar, behaviourally opposite

john.address.city = 'Malibu' mutates a shared object. john.address = { city: 'Malibu' } re-aims a wire. Always read the left side of an assignment carefully before deciding what a line does.

03

let vs const

const freezes the wire, not the object. You can still mutate what a constant points at.

const shrek = { species: 'ogre' };
shrek = fiona;            // TypeError — can't re-aim a const wire
shrek.species = 'human'; // fine! mutating the object is allowed
console.log(shrek.species);  // 'human'

A const variable's wire is read-only: it must keep pointing at the same value. But if that value is an object, the object's own properties can still be mutated. Teams debate how much to lean on const — some ban let entirely, others trust programmers to reassign. Whatever the style, remember the precise rule: const prevents reassignment, not mutation.

04

Debugging with the model

A good mental model narrows the suspects. If a value changed, the wires tell you the only ways it could have happened.

Sherlock said: "When you have eliminated the impossible, whatever remains, however improbable, must be the truth." If sherlock.address.city has changed, the diagram allows exactly three explanations:

  1. The sherlock variable was reassigned to a different object.
  2. The object reached via sherlock was mutated — its address now points elsewhere.
  3. The object reached via sherlock.address was mutated — its city changed.

It works in reverse too: if code only re-aims the john wire, the model proves it cannot affect any chain starting at sherlock — so it's not the culprit. The model gives you theories; confirm them with console.log or a debugger.

Mutating just-created objects is safe

An object you just made with {} has no other wires pointing at it yet, so mutating it can't surprise anyone. The risk only appears once a value is shared. Be intentional about what you mutate, and when.

Mutation isn't "bad" — if data changes over time, something is mutated somewhere. The craft is deciding what, where, and when. One common discipline keeps mutation in a narrow layer so the rest of the program stays predictable.

05

Recap

  • Objects are never nested — two {} literals are two separate objects, joined only by a wire.
  • Always read the left side of an assignment. a.b.c = x mutates a shared object; a.b = x re-aims a wire.
  • Mutating an object is visible through every wire pointing at it. Accidentally shared data is the usual source of bugs.
  • Mutating just-created objects is safe. How much you mutate depends on your app's architecture.
  • const stops a variable from being reassigned, but does not stop the object it points at from being mutated.