Spread & Rest with Objects
The spread (...) and rest (...) operators look identical but do opposite jobs. Spread expands an object’s own enumerable properties into a new object literal, making it the cleanest way to clone and merge objects. Rest, used in destructuring, collects leftover properties into a fresh object. Both arrived for objects in ES2018 and are now the idiomatic way to work with object copies in modern JavaScript.
Cloning an object with spread
Placing ...obj inside a new object literal copies every own enumerable property into the new object. The result is a brand-new object with the same key/value pairs — mutating the copy never touches the original.
const user = { id: 1, name: "Ada" };
const copy = { ...user };
copy.name = "Grace";
console.log(user.name); // unchanged
console.log(copy.name);
console.log(copy === user);
Output:
Ada
Grace
false
This replaces older patterns like Object.assign({}, user). The spread form is more concise and works inside any object literal alongside other properties.
Merging objects and override order
You can spread multiple objects into one literal. When two sources share a key, the last one wins — properties are applied left to right, and later values overwrite earlier ones. This makes spread perfect for applying defaults.
const defaults = { theme: "light", fontSize: 14, debug: false };
const userPrefs = { theme: "dark", fontSize: 16 };
const settings = { ...defaults, ...userPrefs };
console.log(settings);
Output:
{ theme: 'dark', fontSize: 16, debug: false }
Because order matters, place the object whose values should take precedence last. You can also mix spreads with literal properties to override or add a single key inline.
const settings = { ...defaults, ...userPrefs, debug: true };
Tip: To apply defaults behind user input, spread the defaults first and the user object last. Reversing the order would let stale defaults clobber real values.
The shallow-copy caveat
Spread performs a shallow copy. Top-level primitive values are copied, but nested objects and arrays are copied by reference — the clone and the original still point to the same nested object. Mutating that nested value affects both.
const original = {
name: "Ada",
roles: ["admin"],
address: { city: "London" },
};
const clone = { ...original };
clone.address.city = "Paris";
console.log(original.address.city); // also changed!
Output:
Paris
For an independent deep copy, use the built-in structuredClone() (available in modern browsers and Node 17+), which recursively duplicates nested structures.
const deep = structuredClone(original);
deep.address.city = "Berlin";
console.log(original.address.city);
console.log(deep.address.city);
Output:
London
Berlin
| Technique | Depth | Notes |
|---|---|---|
{ ...obj } | Shallow | Fast, copies own enumerable props |
Object.assign({}, obj) | Shallow | Triggers setters on the target |
structuredClone(obj) | Deep | No functions/DOM nodes; clones Maps, Sets, Dates |
JSON.parse(JSON.stringify(obj)) | Deep | Loses functions, undefined, Date becomes string |
Rest in object destructuring
Rest does the inverse of spread. In a destructuring pattern, a trailing ...name gathers every property that wasn’t already named into a new object. It is ideal for pulling out one field while keeping “everything else” intact.
const user = { id: 1, name: "Ada", password: "s3cr3t", role: "admin" };
const { password, ...safeUser } = user;
console.log(safeUser);
Output:
{ id: 1, name: 'Ada', role: 'admin' }
The rest property must come last in the pattern, and like spread it produces a shallow copy of the remaining own enumerable properties. This pattern shines for removing keys immutably (e.g. stripping a password before sending a response) without mutating the source object.
You can combine renaming, defaults, and rest in a single destructure:
function configure({ retries = 3, ...rest }) {
console.log(retries, rest);
}
configure({ retries: 5, timeout: 1000, cache: true });
configure({ timeout: 500 });
Output:
5 { timeout: 1000, cache: true }
3 { timeout: 500 }
Spread vs rest at a glance
| Aspect | Spread | Rest |
|---|---|---|
| Where it appears | Inside an object literal | Inside a destructuring pattern |
| Direction | Expands properties out | Collects properties in |
| Position | Anywhere in the literal | Must be last |
| Produces | A new merged object | A new object of leftovers |
Best Practices
- Reach for
{ ...obj }to copy or merge instead of mutating an existing object — favoring immutable updates makes state easier to reason about. - Remember override order: put the highest-priority source object last in the spread.
- Treat spread as shallow; use
structuredClone()when nested objects must be fully independent. - Use rest destructuring (
const { secret, ...rest } = obj) to omit keys without mutating the original. - Keep the rest element last in any destructuring pattern, or the syntax will throw.
- Avoid spreading inside hot loops over huge objects; each spread allocates a new object and copies every key.