Spread and Rest
Learn how the spread and rest operators work with objects in JavaScript.
Spread and Rest
We already covered spread and rest with arrays in detail. With objects they work the same way — same ... syntax, same idea — but applied to properties instead of array items.
- Spread — expands an object's properties into another object
- Rest — collects remaining properties into a new object
Spread With Objects
Copying an Object
The most common use — making a real independent copy of an object.
const user = { name: "Ali", age: 22 };
const copy = { ...user };
copy.name = "Sara";
console.log(user.name); // Ali — unchanged
console.log(copy.name); // SaraWithout spread, copy = user would make both point to the same object in memory.
Merging Objects
Combine two or more objects into one new object.
const personal = { name: "Ali", age: 22 };
const professional = { role: "Developer", company: "DocsHub" };
const profile = { ...personal, ...professional };
console.log(profile);
// { name: "Ali", age: 22, role: "Developer", company: "DocsHub" }Overwriting Properties
When spreading multiple objects with the same key, the last one wins.
const defaults = { theme: "light", language: "en", fontSize: 14 };
const userSettings = { theme: "dark", fontSize: 16 };
const settings = { ...defaults, ...userSettings };
console.log(settings);
// { theme: "dark", language: "en", fontSize: 16 }theme and fontSize from userSettings overwrite the ones from defaults. language stays because userSettings does not have it.
This is the standard pattern for applying user preferences over default settings.
Adding or Updating Properties
Spread and add new properties at the same time — without touching the original.
const user = { name: "Ali", age: 22 };
// Add a new property
const withCity = { ...user, city: "Lahore" };
console.log(withCity); // { name: "Ali", age: 22, city: "Lahore" }
console.log(user); // { name: "Ali", age: 22 } — unchanged
// Update an existing property
const older = { ...user, age: 23 };
console.log(older); // { name: "Ali", age: 23 }
console.log(user); // { name: "Ali", age: 22 } — unchangedThe property after the spread overwrites the one inside it. Order matters — put the override after the spread.
// ❌ Wrong order — spread overwrites your value
const wrong = { age: 23, ...user };
console.log(wrong.age); // 22 — user.age spread over your 23
// ✅ Correct order — your value overwrites the spread
const correct = { ...user, age: 23 };
console.log(correct.age); // 23Removing a Property
Spread cannot remove a property directly — but combine it with rest destructuring and you can:
const user = { name: "Ali", age: 22, password: "secret123", city: "Lahore" };
const { password, ...safeUser } = user;
console.log(safeUser);
// { name: "Ali", age: 22, city: "Lahore" }
console.log(password); // secret123 — separated out, not in safeUserYou destructure out the property you want to remove, and collect the rest into a new clean object. This is the standard way to exclude a sensitive field before sending data somewhere.
Rest With Objects
Rest in object destructuring collects all properties that were not explicitly destructured into a new object.
const user = { name: "Ali", age: 22, city: "Lahore", role: "admin" };
const { name, role, ...details } = user;
console.log(name); // Ali
console.log(role); // admin
console.log(details); // { age: 22, city: "Lahore" }Real use — separating concerns
const request = {
method: "POST",
url: "/api/users",
body: { name: "Ali", email: "ali@example.com" },
headers: { "Content-Type": "application/json" },
timeout: 5000
};
const { method, url, ...config } = request;
console.log(method); // POST
console.log(url); // /api/users
console.log(config); // { body: {...}, headers: {...}, timeout: 5000 }method and url are pulled out for routing. Everything else stays together in config to be passed along.
Shallow Copy Warning
Both spread and Object.assign() create shallow copies. If an object has nested objects, those nested ones are still shared by reference.
const user = {
name: "Ali",
address: {
city: "Lahore",
country: "Pakistan"
}
};
const copy = { ...user };
copy.name = "Sara"; // ✅ only changes copy
copy.address.city = "Karachi"; // ❌ changes both — address is shared
console.log(user.name); // Ali — unchanged
console.log(user.address.city); // Karachi — changed!The top level is copied. But address is an object — and both user and copy point to the same address object in memory.
For a true deep copy of a simple object with no functions or special values:
const deepCopy = JSON.parse(JSON.stringify(user));This works for plain data objects. For complex objects with functions, dates, or circular references — you need a library like Lodash's cloneDeep. We cover this in the Advanced section.
Always be aware of shallow vs deep copying when working with nested objects. Spreading looks like a full copy but nested objects are still shared.
A Real Example — Updating State
In React and similar frameworks, you never mutate state directly — you always create a new object with the updated value. Spread makes this clean and easy.
const state = {
user: "Ali",
theme: "light",
notifications: true,
language: "en"
};
// Toggle theme
const newState = { ...state, theme: state.theme === "light" ? "dark" : "light" };
console.log(newState.theme); // dark
console.log(state.theme); // light — original unchanged// Update a nested property without mutating
const userProfile = {
name: "Ali",
settings: {
theme: "light",
language: "en"
}
};
const updated = {
...userProfile,
settings: {
...userProfile.settings,
theme: "dark"
}
};
console.log(updated.settings.theme); // dark
console.log(userProfile.settings.theme); // light — unchangedSpread the outer object, then spread the nested object with the override. This pattern is everywhere in real React code.
Spread vs Object.assign()
Both merge objects — spread is the modern preferred way.
const a = { x: 1 };
const b = { y: 2 };
// Object.assign — old way
const merged1 = Object.assign({}, a, b);
// Spread — modern way
const merged2 = { ...a, ...b };
console.log(merged1); // { x: 1, y: 2 }
console.log(merged2); // { x: 1, y: 2 }Object.assign() | Spread ... | |
|---|---|---|
| Syntax | Verbose | Clean |
| Returns new object | Only with {} as first arg | Always |
| Modifies target | Yes if target is existing object | Never |
| Modern JavaScript | Old but valid | Preferred |
Use spread in modern code — it is shorter, clearer, and never accidentally mutates an existing object.
Summary
- Spread expands object properties into another object —
{ ...obj } - Use spread to copy objects without mutation
- Use spread to merge objects — last property wins on conflicts
- Use spread to add or update properties — put overrides after the spread
- Combine rest destructuring with spread to remove a property —
const { key, ...rest } = obj - Rest in destructuring collects remaining properties into a new object
- Spread creates shallow copies — nested objects are still shared by reference
- Use
{ ...outer, nested: { ...outer.nested, key: value } }to safely update nested properties