References: Copy vs. Alias

Intermediate · 18 min read · ▶ live playground · ✦ checkpoint

Objects and arrays feel like ordinary values until two names change the same thing. That surprise is the reference model. In JavaScript, assigning an object does not copy the object; it copies a reference, the engine's handle to the object.

Objects live behind references

Primitive values such as numbers and strings copy directly. Object values are different: the variable holds a reference to an object, not the object body itself.

let score = 18;
let copiedScore = score;
 
copiedScore = 99;
 
const player = {
  name: "Mina",
  stats: {
    score: 18,
  },
};
 
const alias = player;
alias.stats.score = 99;
 
console.log(score);
console.log(copiedScore);
console.log(player.stats.score);
console.log(alias.stats.score);

18, 99, then 99, 99.

The number assignment made an independent value copy. Changing copiedScore did not change score.

The object assignment made an alias. player and alias are two names holding the same reference, so they reach the same object. Changing the nested score through alias is visible through player.

= copies the reference

This is the trap that causes the most bugs in beginner object code.

const original = {
  name: "Mina",
  stats: {
    score: 18,
  },
};
 
const copied = original;
 
copied.name = "Rae";
copied.stats.score = 42;
 
console.log(original.name);
console.log(original.stats.score);
console.log(copied.name);
console.log(copied.stats.score);

Rae, 42, then Rae, 42.

This is why naming matters. copied is a bad name in that example because no object copy happened. alias would be honest.

Equality checks identity

Objects compare by identity: "are these the exact same object?" They do not compare by matching contents.

const first = {};
const second = {};
const same = first;
 
console.log(first === second);
console.log(first === same);
console.log({} !== {});

false, true, then true.

first and second look the same, but they are two different object literals, so first === second is false. same holds the same reference as first, so first === same is true.

That last line, {} !== {}, is true for the same reason: each {} creates a new object. Matching shape is not identity. Later tools and libraries can compare object contents, but JavaScript's built-in === asks whether both sides are the very same object.

Shallow copies vs deep copies

Spread from 7.2 creates a shallow copy: a new top-level object or array, with the same property values placed inside. If one of those values is another object or array, the nested value is still shared.

A deep copy creates new nested objects and arrays too. Modern JavaScript gives you structuredClone(...) for many data-shaped values.

const original = {
  name: "Mina",
  stats: {
    score: 18,
    badges: ["starter"],
  },
};
 
const shallow = {
  ...original,
};
 
const deep = structuredClone(original);
 
shallow.name = "Rae";
shallow.stats.score = 99;
deep.stats.badges[0] = "cloned";
 
console.log(original.name);
console.log(shallow.name);
console.log(original.stats.score);
console.log(deep.stats.score);
console.log(original.stats.badges[0]);
console.log(deep.stats.badges[0]);

Mina, Rae, 99, 18, starter, then cloned.

The shallow copy got its own top-level name, so changing shallow.name did not change original.name. But shallow.stats still shared the nested object, so changing shallow.stats.score changed original.stats.score.

The deep copy made its own nested stats object and badges array. Changing deep.stats.badges[0] did not change original.stats.badges[0].

structuredClone is for data, not behavior. It is a good fit for plain objects, arrays, numbers, strings, booleans, null, and similar data values. If your object has methods, keep the method design separate instead of expecting clone tools to preserve behavior.

Try it — predict the shared parts

Run this once, then change the mutations one at a time. At the prompt, inspect original, alias, shallow, and deep.

javascript — live consolereal engine
⌘/Ctrl + Enter to run

The top-level name in shallow is separate. The nested stats object and badges array are shared between original, alias, and shallow. The deep object has its own nested copies.

Object.freeze and immutability

Immutability means treating data as unchanging after creation. JavaScript cannot make every object in your program magically immutable, but Object.freeze(object) can freeze one object's top-level shape.

const settings = Object.freeze({
  theme: "dark",
  shortcuts: {
    save: true,
  },
});
 
settings.shortcuts.save = false;
 
console.log(Object.isFrozen(settings));
console.log(settings.theme);
console.log(settings.shortcuts.save);
console.log(Object.isFrozen(settings.shortcuts));

true, dark, false, then false.

Object.freeze(settings) freezes the top-level settings object. Top-level writes are ignored in sloppy scripts or throw in strict mode, but either way the frozen top-level value does not change.

But freeze is shallow. The nested shortcuts object was not frozen, so settings.shortcuts.save = false; still changed it. If you want deeply immutable data, every nested object needs its own strategy. That bigger style of writing code comes back in Section 13; for now, know that freeze is a shallow guard and a design signal.

Checkpoint

Answer all three to mark this lesson complete

You now have the model behind the earlier surprises: objects live behind references, assignment can make aliases, spread is shallow, identity is not shape, and freeze only guards the top level. Next, 7.5 turns these object skills toward JSON, the data format you will see in files, APIs, and almost every real JavaScript app.

+50 XP on completion