Objects & references
Learn why two variables can point to the same object, how object identity affects functions and comparisons, and when shallow or deep copies are the safer choice.
- 01Trace shared objectsExplain why changing one variable’s object can appear through another variable.
- 02Predict calls and comparisonsTell when a function mutation escapes and when
===checks identity. - 03Choose the right copyUse spread,
Object.assign, orstructuredClonewith the right expectations.
One object can have many names
In the Data types lesson you met the big split: primitives copy the value, while objects copy a reference. This lesson slows that sentence down until you can predict it in real programs. If two variables point at the same object, changing the object through one variable is visible through the other variable too.
A reference is the value JavaScript uses to reach an object. You do not see the reference directly; you see the object it leads to. For this lesson, imagine the reference as an address written on an envelope.
Object variables do not contain the whole object. They contain a way to find it. Copying the variable copies that way to find it, not a brand-new object.
If two people hold envelopes with the same home address, both visit the same house. Copying the address did not build another house. If one person repaints the door, the other person sees the new color because there is still only one house.
- In real life: A house
- In JavaScript: The object
- In real life: An address on an envelope
- In JavaScript: A reference in a variable
- In real life: Copying the address
- In JavaScript:
const b = a; - In real life: Repainting the house
- In JavaScript: Changing a property like
b.score = 2
Where the analogy stops: JavaScript references are not postal addresses and you cannot inspect or do math with them. The analogy only explains why two names can lead to one object.
This matters in arrays, settings objects, function parameters, state updates, and any code that tries to avoid accidental mutation. Later lessons named Memory: primitives vs objects, Freezing, sealing & immutability, Immutability, and Messaging & structured cloning go deeper. Today we build the everyday mental model.
Values vs references
INTERACTIVEPrimitives such as numbers, strings, booleans, null, and undefined behave like self-contained values. If you copy a number, changing one variable later does not change the other. Objects are different: the copied value is a reference to the same object.
let x = 1;
let y = x;
y = 2;
console.log(x); // 1
const a = { score: 1 };
const b = a;
b.score = 2;
console.log(a.score); // 2The diagram below uses real objects and real property values, then draws a conceptual picture: variable names on the left, object cards on the right. It is not engine memory inspection; it is a teaching picture of the references your code created.
const a = { score: 1 };const b = a;b.score = 2;const c = { ...a };c.score = 3;const d = { ...a, stats: { ...a.stats } };→ #1→ #1{ score: 1 }b receives the same reference as a.
Notice the two separate questions. Are two variables pointing at the same object? And if there is a copy, are any nested objects still shared? We will return to the nested case in the shallow-copy section.
Call by sharing
STEP THROUGHFunction parameters follow the same rule. When you pass an object to a function, the parameter receives a copy of the reference. That means the function can mutate the shared object. But if the function reassigns the parameter, only that local parameter points somewhere new.
This behavior is often called call by sharing. It is not pass-by-reference in the sense that the function can replace the caller’s variable. It shares the object, not the caller’s binding.
Switch the function body. A property write escapes; a parameter reassignment does not.
script
function addBadge(user) { user.badge = "mentor";} addBadge(ada);console.log(ada.badge);A friend with your key can enter the same house and move a chair. You will see that change later. But if your friend replaces the key on their own keyring, your keyring does not change.
- In real life: Handing a friend your house key
- In JavaScript: Passing an object reference to a parameter
- In real life: They rearrange the furniture
- In JavaScript: The function mutates a property
- In real life: They put a different key on their own keyring
- In JavaScript: The function reassigns its parameter
Where the analogy stops: The friend analogy has people and intent. JavaScript just copies a value into a parameter and follows assignments exactly.
Comparing objects
STEP THROUGHThe Comparisons lesson taught that === means strict equality. With objects, strict equality checks identity: are these two references pointing at the exact same object? It does not compare keys and values.
Predict each comparison. Remember: for objects, === asks whether both sides are the same object.
script
const b = a;const c = { score: 1 }; console.log(a === b);console.log(a === c);console.log({ score: 1 } === { score: 1 });| Goal | Example | Important warning |
|---|---|---|
| Same object identity | a === b | True only when both sides point at the same object. |
| Flat contents | Compare keys, then values with Object.is | Works only for the level you check; nested objects need their own rules. |
| Quick JSON string | JSON.stringify(a) === JSON.stringify(b) | Key order can change the string, and JSON loses values like undefined and functions. |
For simple flat objects, a content comparison can be written by checking that both objects have the same keys and each key has the same value. That is a choice your code makes; it is not what === does automatically.
Shallow copies with spread and Object.assign
INTERACTIVEA shallow copy creates a new top-level object and copies the top-level properties into it. The common forms are { ...obj } and Object.assign(, obj). Arrays have the same idea with [...array].
const user = { name: "Ada", address: { city: "Pune" },}; const shallow = { ...user };shallow.address.city = "Kochi"; const deep = structuredClone(user);deep.address.city = "Jaipur"; console.log(user.address.city);console.log(deep.address.city);In that code, shallow is a different top-level object, but shallow.address and user.address are the same nested object. Changing shallow.address.city changes that shared nested object, so the original user appears to change too.
const b = a;afterais an objectconst b = { score: a.score };const b = { ...a };const b = Object.assign({}, a);const queue2 = queue1;whenqueue1is an array- A function parameter receiving an object argument
Decide whether each line leaves two names sharing one object, or creates an independent top-level object.
A shallow copy is like building a new house, then moving in boxes that still contain the same shared furniture. Changing the paint on the new house is independent. Changing the shared sofa is visible in both places.
Deep copies with structuredClone
A deep copy also copies nested data. Modern browsers and recent Node versions provide structuredClone(value), the same family of cloning rules used by web messaging APIs. It handles plain objects, arrays, Dates, Maps, Sets, and many other data values. It does not clone functions or DOM nodes; trying to clone a function throws a DataCloneError.
| Method | What it copies | Watch out |
|---|---|---|
Assignment: const b = a | Only the reference | Both names reach the same object. |
Spread: { ...a } | Top-level own properties | Nested objects stay shared. |
Object.assign({}, a) | Top-level own properties | Same shallow behavior as spread for this use. |
structuredClone(a) | Nested cloneable data | Functions and DOM nodes are not cloneable. |
JSON.parse(JSON.stringify(a)) | JSON-safe data only | Dates become strings; undefined and functions disappear. |
Use structuredClone when you truly need independent nested data. Do not use it as a reflex. Deep copies can be more expensive, and some objects represent things that should not be copied. If you need to update only one branch, copying only the changed path is often clearer.
const doesn’t freeze
The Variables lesson introduced const: it means the variable binding cannot be reassigned. It does not make the object immutable. You can still change properties on a const object, because the variable still points at the same object.
const user = { name: "Ada" };user.name = "Grace"; // allowed// user = { name: "Lin" }; // TypeError if you run itconsole.log(user.name); // "Grace"Object.freeze(user) is a separate tool, and it is shallow: it freezes the object itself, not every nested object inside it. The Freezing, sealing & immutability lesson goes into that topic later.
Where you’ll use this
References show up whenever code shares data. A few everyday examples:
- A function receives a settings object. If it changes
settings.theme, the caller’s object changes too. - UI state often needs a new object so change detection can see that something changed. A shallow copy is common for top-level updates.
- Tests often compare object contents, not object identity, because two correct functions can return equal-looking but different objects.
- Messages between browser contexts use structured cloning rules, which is why Messaging & structured cloning later deserves its own lesson.
Common misconceptions
“Objects are copied when assigned.”
The reference is copied. The object is not copied unless you explicitly create another object.
“JavaScript passes objects by reference.”
Property mutations are shared, but parameter reassignments are local. “Call by sharing” is the more precise beginner-friendly phrase.
“Two objects with the same properties are equal.”
=== compares identity for objects. If you want a content comparison, write or use a content comparison.
“Spread is a deep copy.”
Spread and Object.assign are shallow. Nested objects remain shared unless you copy them too.
“const makes an object safe from changes.”
const protects the binding, not the object. It stops user = ..., not user.name = ....
Practice: objects and references
5 EXERCISESTrace the aliases and predict the console output.
const a = { score: 1 };
const b = a;
b.score = 5;
console.log(a.score);It prints 5. a and b point at the same object, so b.score = 5 changes the object that a.score reads.
The function renames a user by mutating its input. Rewrite it so the original object is not changed.
function renameUser(user, name) {
user.name = name;
return user;
}function renameUser(user, name) {
return { ...user, name };
}{ ...user, name } creates a new top-level object and then sets its name property. The original object is left alone.
Given a nested settings object, make a copy before changing a nested color.
const copy = structuredClone(settings);
copy.theme.color = "blue";const copy = structuredClone(settings);
copy.theme.color = "blue";structuredClone(settings) creates an independent nested copy for cloneable data, so changing copy.theme.color does not change settings.theme.color.
Write or study a function that returns true for flat objects with the same keys and values.
function sameFlatObject(left, right) {
const leftKeys = Object.keys(left);
const rightKeys = Object.keys(right);
return leftKeys.length === rightKeys.length &&
leftKeys.every((key) => Object.hasOwn(right, key) && Object.is(left[key], right[key]));
}function sameFlatObject(left, right) {
const leftKeys = Object.keys(left);
const rightKeys = Object.keys(right);
return leftKeys.length === rightKeys.length &&
leftKeys.every((key) => Object.hasOwn(right, key) && Object.is(left[key], right[key]));
}The function checks key counts and then uses every with Object.hasOwn and Object.is for each flat value. Nested objects would need a deeper comparison.
Run the snippet and explain why the property change is allowed.
const pet = { name: "Momo" };
pet.name = "Pip";
console.log(pet.name);const keeps pet pointing at the same object. It does not freeze that object, so pet.name = "Pip" is allowed and prints Pip.
Quiz: check your understanding
8 QUESTIONSChoose once, then read every explanation. Wrong answers teach the edge cases.
Question 1 of 8What does this aliasing code print?
Read the code, then predictconst a = { score: 1 }; const b = a; b.score = 2; console.log(a.score);Choose an answer to see the explanation.
Question 2 of 8Which sentence best describes call by sharing?
Choose an answer to see the explanation.
Question 3 of 8What does strict equality check for objects?
Choose an answer to see the explanation.
Question 4 of 8What does this identity check print?
Read the code, then predictconst a = { city: "Oslo" }; const b = { city: "Oslo" }; console.log(a === b);Choose an answer to see the explanation.
Question 5 of 8What does a shallow copy with spread copy?
Choose an answer to see the explanation.
Question 6 of 8What does this shallow copy print?
Read the code, then predictconst user = { address: { city: "Rome" } }; const copy = { ...user }; copy.address.city = "Milan"; console.log(user.address.city);Choose an answer to see the explanation.
Question 7 of 8Which value can
structuredClonecopy correctly?Choose an answer to see the explanation.
Question 8 of 8What does
constfreeze inconst user = { name: "Ada" }?Choose an answer to see the explanation.
Key takeaways
- Variables that hold objects hold references to objects.
- Copying an object variable copies the reference, not the object.
- Function parameters share object references, but reassigning the parameter is local.
===compares object identity, not matching contents.- Spread and
Object.assignare shallow;structuredClonecopies cloneable nested data. constprevents reassignment of the binding, not mutation of the object.
Remember the one-liner.
An object variable is a way to reach an object; two variables can reach the same one.
Up next: Methods & this.