Object References and Copying
Objects are reference types.
This is one of the most important ideas to understand before you build larger JavaScript programs.
When you assign an object to another variable, JavaScript does not copy the whole object.
It copies a reference to the same object.
const user = {
name: "Alice",
};
const admin = user;
admin.name = "Ava";
console.log(user.name); // AvaChanging admin.name also changed user.name.
That is because user and admin point to the same object.
Objects Are Stored by Reference
Primitive values behave differently.
let a = 10;
let b = a;
b = 20;
console.log(a); // 10
console.log(b); // 20Numbers are primitive values, so assigning a to b copies the value.
Objects are different:
const first = {
count: 1,
};
const second = first;
second.count = 2;
console.log(first.count); // 2Both variables refer to the same object.
Comparing Objects
Objects are compared by reference.
const user1 = {
name: "Alice",
};
const user2 = {
name: "Alice",
};
console.log(user1 === user2); // falseThe objects have the same contents, but they are different objects in memory.
Now compare this:
const user1 = {
name: "Alice",
};
const user2 = user1;
console.log(user1 === user2); // trueThis is true because both variables reference the same object.
Mutating Objects
To mutate an object means to change the existing object.
const user = {
name: "Alice",
role: "user",
};
user.role = "admin";
console.log(user); // { name: "Alice", role: "admin" }Mutation is not always wrong.
But you must know when it is happening.
Unintentional mutation can create bugs when multiple parts of your program share the same object.
const Does Not Make Objects Immutable
const prevents reassignment.
It does not prevent object mutation.
const user = {
name: "Alice",
};
user.name = "Ava"; // allowed
console.log(user.name); // AvaThis is not allowed:
user = {
name: "Mira",
}; // TypeErrorWith const, the variable cannot point to a different object.
But the object itself can still change.
Creating a Shallow Copy With Spread
Use the object spread syntax to create a shallow copy.
const user = {
name: "Alice",
role: "user",
};
const copy = {
...user,
};
copy.role = "admin";
console.log(user.role); // user
console.log(copy.role); // adminNow user and copy are different top-level objects.
Updating Objects Immutably
Instead of changing the original object, create a new object with updated values.
const user = {
name: "Alice",
role: "user",
};
const updatedUser = {
...user,
role: "admin",
};
console.log(user.role); // user
console.log(updatedUser.role); // adminThis pattern is common in frontend development.
It makes changes easier to track.
Spread Order Matters
Properties written later override earlier properties.
const user = {
name: "Alice",
role: "user",
};
const updatedUser = {
...user,
role: "admin",
};
console.log(updatedUser.role); // adminIf you put the spread last, it overwrites your update.
const updatedUser = {
role: "admin",
...user,
};
console.log(updatedUser.role); // userThe original user.role overwrote "admin".
Shallow Copy Caveat
Object spread creates a shallow copy.
Nested objects are still shared.
const user = {
name: "Alice",
address: {
city: "Delhi",
},
};
const copy = {
...user,
};
copy.address.city = "Mumbai";
console.log(user.address.city); // MumbaiThe top-level object was copied.
But address still points to the same nested object.
Copying Nested Objects
To update nested data immutably, copy each level that changes.
const user = {
name: "Alice",
address: {
city: "Delhi",
country: "India",
},
};
const updatedUser = {
...user,
address: {
...user.address,
city: "Mumbai",
},
};
console.log(user.address.city); // Delhi
console.log(updatedUser.address.city); // MumbaiThe important idea:
Copy the object you are updating and every nested object along the path.Object.assign()
Before object spread became common, developers often used Object.assign().
const user = {
name: "Alice",
role: "user",
};
const copy = Object.assign({}, user);
console.log(copy); // { name: "Alice", role: "user" }This also creates a shallow copy.
Object spread is usually more readable:
const copy = { ...user };Deep Copying
A deep copy copies nested objects too.
For JSON-safe data, you may see:
const deepCopy = JSON.parse(JSON.stringify(original));But this has limitations.
It loses values such as:
- functions
undefinedDateobjectsMapSetBigInt
Modern JavaScript provides structuredClone() for many deep-copy cases.
const original = {
user: {
name: "Alice",
},
};
const copy = structuredClone(original);
copy.user.name = "Ava";
console.log(original.user.name); // AliceUse deep copying only when you really need it.
Often, copying only the changed path is better.
Freezing an Object
Object.freeze() prevents changes to an object's top-level properties.
const settings = Object.freeze({
theme: "dark",
});
settings.theme = "light";
console.log(settings.theme); // darkIn strict mode, trying to change a frozen object can throw an error.
But Object.freeze() is shallow.
const settings = Object.freeze({
theme: {
mode: "dark",
},
});
settings.theme.mode = "light";
console.log(settings.theme.mode); // lightThe nested object was not frozen.
Practical Example: Updating a User
const user = {
id: 1,
name: "Alice",
role: "user",
};
function promoteUser(user) {
return {
...user,
role: "admin",
};
}
const promoted = promoteUser(user);
console.log(user.role); // user
console.log(promoted.role); // adminThis function does not mutate the original object.
That makes it easier to reason about.
Practical Example: Updating an Array of Objects
Objects often appear inside arrays.
const users = [
{ id: 1, name: "Alice", active: true },
{ id: 2, name: "Bob", active: false },
];
const updatedUsers = users.map((user) => {
if (user.id === 2) {
return {
...user,
active: true,
};
}
return user;
});
console.log(updatedUsers);This creates a new array and a new object only for the updated user.
The original users array is not mutated.
Mutation vs Immutable Updates
| Approach | Example | Original changed? |
|---|---|---|
| Mutation | user.role = "admin" |
Yes |
| Immutable update | { ...user, role: "admin" } |
No |
Mutation can be simpler for small scripts.
Immutable updates are often better when state is shared, tracked, or rendered in a UI.
Best Practices
Know when you are mutating an object:
user.name = "Ava";Use spread for simple shallow copies:
const copy = { ...user };Use immutable updates when you want predictable changes:
const updatedUser = { ...user, active: false };Copy nested objects when updating nested data:
const updated = {
...user,
address: {
...user.address,
city: "Mumbai",
},
};Remember that object comparison checks references, not contents.
Common Mistakes
Mistake 1: Thinking Assignment Copies an Object
const user = {
name: "Alice",
};
const copy = user;
copy.name = "Ava";
console.log(user.name); // AvaAssignment copies the reference, not the object.
Mistake 2: Thinking const Makes an Object Immutable
const user = {
name: "Alice",
};
user.name = "Ava"; // allowedconst prevents reassignment, not mutation.
Mistake 3: Forgetting Spread Is Shallow
const user = {
address: {
city: "Delhi",
},
};
const copy = { ...user };
copy.address.city = "Mumbai";
console.log(user.address.city); // MumbaiNested objects are still shared unless you copy them too.
Mistake 4: Comparing Objects by Contents
console.log({ name: "Alice" } === { name: "Alice" }); // falseThey look the same, but they are two different objects.
Quick Check
What does this log?
const a = {
count: 1,
};
const b = a;
b.count = 2;
console.log(a.count);It logs:
2What does this log?
const a = {
count: 1,
};
const b = {
...a,
};
b.count = 2;
console.log(a.count);It logs:
1Summary
Objects are reference types.
- Assigning an object to another variable copies the reference.
- Mutating one reference changes the shared object.
- Objects are compared by reference, not by contents.
constprevents reassignment, not object mutation.- Object spread creates a shallow copy.
- Nested objects must be copied separately when updating nested data.
- Immutable updates create new objects instead of changing existing ones.
JSON.parse(JSON.stringify(...))can deep-copy only JSON-safe data.structuredClone()can help with many deep-copy cases.