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.
- 01Follow shared objectsExplain why a change made through one variable can show up through another variable.
- 02Predict function calls and comparisonsTell when a function's change reaches your object, and know that
===checks for the very same object. - 03Choose the right copyKnow how much spread,
Object.assign, andstructuredCloneeach copy.
One object can have many names
The Data types lesson said two things. When you copy a number or a string, you copy the value itself. When you copy an object, you copy a reference to it. This lesson shows what that means in real code.
A reference is how JavaScript finds an object. Think of it as the object's address. You never see the reference itself. You only see the object it leads to.
A variable that holds an object doesn't hold the whole object. It holds a reference: a way to find the object. When you copy that variable, you copy the reference. You don't get a new object. So two variables can point at the same object. A change made through one name shows up through the other.
const cart = { item: "tea" };
const checkoutCart = cart;
checkoutCart.item = "coffee";
console.log(cart.item); // coffeeLine 1 makes one cart object with the item "tea". Line 2 copies the reference into checkoutCart. It does not make a second cart. Line 3 changes the item to "coffee" through checkoutCart. Line 4 reads the item through cart and prints coffee, because there is only one cart.
cart and checkoutCart both point at the same object. There is only one cart. Line 3 changes its item through checkoutCart, so cart sees "coffee" too.Your family keeps one shopping list on a shared app. You have a shortcut to it on your phone. Your sister adds a shortcut on her phone too. That gives you two shortcuts, but there is still only one list. If she changes an item, you see the change when you open the list.
- In real life: The one shopping list
- In JavaScript: The object
- In real life: A shortcut to the list on a phone
- In JavaScript: A reference stored in a variable
- In real life: Your sister adds her own shortcut
- In JavaScript:
const checkoutCart = cart; - In real life: She changes an item on the list
- In JavaScript:
checkoutCart.item = "coffee";
This rule shows up all the time in real code:
- arrays, which are objects too
- settings objects passed around a program
- objects passed to functions
- code that must not change data by accident
Later lessons go deeper: Memory: primitives vs objects, Freezing, sealing & immutability, Immutability, and Messaging & structured cloning. This lesson covers the everyday rules.
Copying a value vs copying a reference
INTERACTIVEPrimitives are the simple values: numbers, strings, booleans, null, and undefined. When you copy a primitive, you get your own copy of the value. Changing one variable later does not change the other. Objects work differently. When you copy an object variable, you copy the reference, so both variables point at 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); // 2Lines 1 to 4 copy a number. y gets its own 1, so line 3 changes only y, and line 4 prints 1. Lines 6 to 9 copy an object variable. b points at the same object as a. Line 8 changes score through b, so line 9 prints 2.
The playground below runs real code step by step. For each step it shows two lists:
- on the left, each variable and the object it points at
- on the right, each object, with a number like #1 so you can tell them apart
The numbers are only labels for this lesson. They are not real memory addresses.
const a = { score: 1 };const b = a;b.score = 2;const c = { ...a };c.score = 3;a.stats = { level: "bronze" };const d = { ...a };d.stats.level = "silver";→ #1→ #1{ score: 1 }Line 2 copies the reference, so a and b both point at object #1.
Ask two questions at each step. First: do two variables point at the same object? Second: if there is a copy, does it still share an object stored inside it? Step 5 shows the second case. The section on shallow copies explains it.
Passing an object to a function
STEP THROUGHFunction parameters follow the same rule. When you pass an object to a function, the parameter gets a copy of the reference. So the parameter and your variable point at the same object. This means:
- If the function changes a property of the object, you see that change after the call.
- If the function gives the parameter a whole new value, like
user = { ... }, only the parameter points somewhere new. Your variable does not change.
This rule is called call by sharing: the function shares your object, but not your variable. Some people call it "pass by reference". That name is not quite right. With true pass by reference, a function could make your variable point at a different object, and JavaScript can't do that.
function markDone(todo) {
todo.done = true;
}
const todo = { done: false };
markDone(todo);
console.log(todo.done); // trueLines 1 to 3 make a function, markDone. Line 2 sets the done property of the object it gets. Line 5 makes a todo object with done: false. Line 6 passes it to the function, so the parameter points at the same object. Line 7 prints true, because the function changed that one object.
In the replay below, you can switch the function body. Compare the two versions:
- Change a property: line 2 adds
badgeto the shared object, soada.badgeis"mentor". - Give the parameter a new object: line 2 points
userat a different object, soada.badgestaysundefined.
Switch the function body. Changing a property reaches ada's object. Giving user a new object does not.
script
function addBadge(user) { user.badge = "mentor";} addBadge(ada);console.log(ada.badge);user pointed at the same object as ada. Then user = { ... } pointed user at a new object. ada did not move, so ada.badge is still undefined.You give a helper a shortcut to your shopping list. If the helper changes an item, you see it, because it is your list. If the helper starts a brand-new list of their own, your list stays the same.
- In real life: You give a helper a shortcut to your list
- In JavaScript: Passing an object to a function
- In real life: The helper changes an item on your list
- In JavaScript: The function changes a property
- In real life: The helper starts a new list of their own
- In JavaScript: The function gives the parameter a new object
Comparing objects
STEP THROUGHThe Comparisons lesson taught ===, strict equality. For objects, === asks one question: "Do both sides point at the very same object?" This is called checking identity. It does not look at the keys and values inside.
const first = { score: 1 };
const second = { score: 1 };
console.log(first === second); // falseLine 1 makes an object with score: 1. Line 2 makes another object with score: 1. Each pair of curly braces { } makes a brand-new object. So these are two objects that look alike. Line 3 prints false, because they are not the same object.
=== asks "is this the very same object?", so the answer is false.Guess each result first. For objects, === asks: are both sides the very same object?
script
const b = a;const c = { score: 1 }; console.log(a === b);console.log(a === c);console.log({ score: 1 } === { score: 1 });Sometimes you do want to know if two objects hold the same things. The table shows three ways to compare. Read each row as: "to answer this question, write this, and watch out for this."
| What you want to know | What to write | Watch out |
|---|---|---|
| Is it the very same object? | a === b | It is true only when both sides point at one object. |
| Do they hold the same keys and values, one level deep? | Compare the keys, then each value with Object.is | It only checks the top level. Objects stored inside need their own check. |
| Quick check: do they turn into the same text? | JSON.stringify(a) === JSON.stringify(b) | Keys in a different order give different text. The text also leaves out undefined and functions. |
For a flat object, one with no objects stored inside it, you can compare contents yourself. Check that both objects have the same keys, and that each key has the same value. You have to write that check. === never does it for you.
Shallow copies with spread and Object.assign
INTERACTIVEA shallow copy makes a new outer object and copies each top-level property into it. If a property holds another object, only the reference to that inner object is copied. There are three common ways to make one:
{ ...obj }, called spread, for objectsObject.assign(, obj), which copies the properties ofobjinto a new empty object[...array], spread for arrays
const user = { name: "Asha", delivery: { note: "Leave at door" },}; const shallow = { ...user };shallow.delivery.note = "Ring bell"; const deep = structuredClone(user);deep.delivery.note = "Call first"; console.log(user.delivery.note);console.log(deep.delivery.note);Lines 1 to 4 make a user object. Its delivery property holds a second object, with a note. Line 6 makes shallow, a new outer object. But shallow.delivery and user.delivery point at the same inner object. Line 7 changes the note through shallow, so user sees the new note too. Line 12 prints Ring bell.
{ ...user } makes a new outer cart, shallow. But its delivery is the same inner object as user.delivery. So changing the note through shallow changes it for user too.Lines 9 and 10 use structuredClone, which copies the inner object too. The next section explains it. Line 13 prints Call first, and the note in user stays Ring bell.
const drinks = ["tea"];
const nextDrinks = [...drinks];
nextDrinks.push("coffee");
console.log(drinks.length); // 1
console.log(nextDrinks.length); // 2Line 2 makes a new array, nextDrinks, with the same items. Line 3 adds "coffee" to the new array only. So line 4 prints 1 and line 5 prints 2. If the array held objects, both arrays would still share those objects.
Good to know: push, sort, and reverse change the array you call them on. To keep the old array as it was, copy it first with [...drinks]. If you only need a sorted result, use toSorted(). It gives back a new sorted array and leaves the old one alone.
const b = a;whenaholds an objectconst b = { score: a.score };const b = { ...a };const b = Object.assign({}, a);const queue2 = queue1;whenqueue1is an array- A function parameter that gets an object passed in
Sort each line. Does it leave two names pointing at one object, or does it make a new outer object?
A shallow copy gives you a new outer object. Adding or changing a top-level property on the copy does not touch the original. But an object stored inside is still shared. Change something inside it, and both copies see the change.
Deep copies with structuredClone
A deep copy copies the objects stored inside too, at every level. Nothing is shared with the original. The built-in function structuredClone(value) makes a deep copy. It works in modern browsers and in recent versions of Node.
It can copy most kinds of data:
- plain objects and arrays
- dates (
Date), and the collectionsMapandSet - many other built-in data types
It cannot copy functions or page elements (DOM nodes). If you try to copy a function, it throws an error called DataCloneError.
const settings = { theme: { color: "blue" } };
const copy = structuredClone(settings);
copy.theme.color = "green";
console.log(settings.theme.color); // blue
console.log(copy.theme.color); // greenLine 1 makes settings, with a theme object inside. Line 2 makes a deep copy, so the copy gets its own theme object. Line 3 changes the color in the copy only. Line 4 prints blue and line 5 prints green.
The table compares five ways to copy an object. Read each row as: "this way copies this much, and watch out for this."
| How you copy | What gets copied | Watch out |
|---|---|---|
Plain assignment: const b = a | Only the reference. No new object is made. | Both names point at the same object. |
Spread: { ...a } | The top-level properties, into a new object. | Objects stored inside are still shared. |
Object.assign({}, a) | The top-level properties, into a new object. | It is shallow, just like spread. |
structuredClone(a) | Everything, at every level. | It can't copy functions or page elements (DOM nodes). |
JSON.parse(JSON.stringify(a)) | Only data that JSON text can hold. | Dates turn into strings. undefined values and functions are dropped. |
Use structuredClone only when you really need a fully separate copy. Don't use it for every change:
- A deep copy takes more work, because it copies everything.
- Some objects, like a page element, should not be copied at all.
- If you change only one part, you can copy just the objects on the way to that part. The "Where you'll use this" section shows how.
const doesn’t freeze an object
The Variables lesson introduced const. It means you can't give the variable a new value later. It does not lock the object. You can still change the properties of a const object, because the variable keeps pointing at the same object.
const user = { name: "Ada" };user.name = "Grace"; // allowed// user = { name: "Lin" }; // TypeError if you run itconsole.log(user.name); // "Grace"Line 1 makes a user object. Line 2 changes its name, and that is allowed. Line 3 is turned off with //. If you ran it, it would throw a TypeError, because it tries to point user at a new object. Line 4 prints "Grace".
To stop changes to the object itself, there is a separate tool: Object.freeze(user). It only locks the outer object, not the objects stored inside it. The Freezing, sealing & immutability lesson covers it later.
Where you’ll use this
References matter whenever two parts of a program use the same data. Some everyday examples:
- A function gets a settings object. If it changes
settings.theme, the object you passed in changes too. - Many page frameworks only notice a change when they get a new object. So code often makes a shallow copy with the change, instead of changing the old object.
- Tests usually compare what is inside two objects, not whether they are the same object. Two correct functions can return objects that look alike but are different objects.
- Sending data between browser windows or workers copies it with the same rules as
structuredClone. The Messaging & structured cloning lesson covers this later.
Take care when you change something deep inside an object. Say a cart has an item object inside it. If you copy only the cart, the old cart and the new cart still share that item. So make a new object at each level you change, and put the new value in.
const cart = { item: { name: "tea", quantity: 1 },}; const nextCart = { ...cart, item: { ...cart.item, quantity: 2 },}; console.log(cart.item.quantity);console.log(nextCart.item.quantity);Lines 1 to 3 make a cart with one item, tea, with quantity 1. Line 5 starts a new object, nextCart. Line 6 copies the cart's properties into it. Line 7 replaces item with a new item object: it copies the old item's properties, then sets quantity to 2. Line 10 prints 1 and line 11 prints 2. The old item never changed.
Common mistakes
“Assigning an object makes a copy of it.”
No. const b = a copies the reference. You get a new object only when you make one, for example with { ...a }.
“JavaScript passes objects by reference.”
Not quite. A function can change the properties of an object you pass in. But if it gives its parameter a new object, your variable doesn't change. The right name for this is "call by sharing".
“Two objects with the same properties are equal.”
Not with ===. For objects, === only checks whether both sides are the same object. To compare what is inside, write a check that looks at the keys and values.
“Spread makes a deep copy.”
No. Spread and Object.assign make shallow copies. Objects stored inside are still shared, unless you copy them too.
“const stops an object from changing.”
No. const only stops the variable from getting a new value. It stops user = ..., but not user.name = ....
Practice: objects and references
6 EXERCISESBefore each one, picture the arrows: which variables point at which object? Then ask what each line does. Does it change something inside an object, point a variable at a new value, or make a copy?
Follow the arrows from a and b. What number does the code print?
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.
This function renames a user by changing the object it gets. Rewrite it so it returns a renamed copy instead, and the object passed in stays the same. What does the function return?
function renameUser(user, name) {
user.name = name;
return user;
}function renameUser(user, name) {
return { ...user, name };
}{ ...user, name } makes a new object with all of the user's properties, then sets name. A property written later wins, so the new object gets the new name. The object passed in stays the same.
A settings object has a theme object inside it. Make a copy, then change the color in the copy only. What call makes the copy?
const copy = structuredClone(settings);
copy.theme.color = "blue";const copy = structuredClone(settings);
copy.theme.color = "blue";structuredClone(settings) makes a deep copy, so the copy gets its own theme object. Changing copy.theme.color leaves settings.theme.color as it was.
Read this function. It should return true for two flat objects with the same keys and values. What does it return for { a: 1 } and { a: 1 }?
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]));
}Object.keys lists each object's keys, and the function compares how many there are. Then every checks each key: Object.hasOwn makes sure the right object has it, and Object.is checks that the values match. This only works one level deep. Objects stored inside would need their own check.
Run the code. Then explain in a few words why const lets line 2 change the name.
const pet = { name: "Momo" };
pet.name = "Pip";
console.log(pet.name);const keeps pet pointing at the same object. It does not lock that object, so pet.name = "Pip" is allowed and the code prints Pip.
A shopping page must change the quantity of one cart item. The old cart must stay as it was. Complete the function, then call it with a cart that holds { item: { name: "tea", quantity: 1 } } and the new quantity 2. Which property needs its own spread?
function setQuantity(cart, quantity) {
return {
...cart,
item: { ...cart.item, quantity },
};
}function setQuantity(cart, quantity) {
return {
...cart,
item: { ...cart.item, quantity },
};
}The outer spread makes a new cart. The inner spread makes a new item with the new quantity. The old cart and the old item keep their quantity.
Quiz: check your understanding
8 QUESTIONSPick an answer, then read the explanation for every choice. The wrong choices show common mistakes. For copy questions, ask first: is the changed property at the top level, or inside an object stored inside?
Question 1 of 8What does this code print when two names share one object?
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 8What happens when you pass an object to a function?
Choose an answer to see the explanation.
Question 3 of 8What does
===check when both sides are objects?Choose an answer to see the explanation.
Question 4 of 8What does this comparison print?
Read the code, then predictconst a = { item: "tea" }; const b = { item: "tea" }; 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 spread copy print?
Read the code, then predictconst user = { delivery: { note: "Leave at door" } }; const copy = { ...user }; copy.delivery.note = "Ring bell"; console.log(user.delivery.note);Choose an answer to see the explanation.
Question 7 of 8Which of these can
structuredClonecopy?Choose an answer to see the explanation.
Question 8 of 8What does
constlock inconst user = { name: "Ada" }?Choose an answer to see the explanation.
Key takeaways
A variable that holds an object holds a reference to it. Copies, function calls, comparisons, and const all follow from that one rule.
- A variable that holds an object holds a reference: a way to find the object.
- Copying that variable copies the reference. It does not make a new object.
- A function can change the properties of an object you pass in. Giving its parameter a new object changes only the parameter.
- For objects,
===checks whether both sides are the same object. It does not compare what is inside. - Spread and
Object.assignmake shallow copies: objects stored inside are still shared.structuredClonemakes a deep copy. conststops the variable from getting a new value. It does not stop changes inside the object.
Remember the one-liner.
A variable that holds an object holds the way to find it, and two variables can find the same one.
Up next: Methods & this. Objects can hold functions too, and you'll learn how a function knows which object it belongs to.