Object Spread & Rest
The three dots ... look the same in two very different positions. Spread unpacks an object's properties into another object literal. Rest collects the leftover properties when destructuring. Together they replace a whole generation of helper libraries and become the standard way to copy, merge, and reshape objects.
Spread for shallow copy
{ ...obj } creates a new object with the same own enumerable string-keyed properties. It is the modern idiom for cloning a plain object one level deep.
const original = { id: 1, name: "Ada", age: 36 };
const copy = { ...original };
copy.age = 99;
console.log(original.age); // 36 — untouched
console.log(copy.age); // 99Merging objects
When two spreads share a key, the later one wins. This makes spread perfect for layering defaults under user overrides.
const defaults = { theme: "light", fontSize: 14, showSidebar: true };
const userPrefs = { fontSize: 18, showSidebar: false };
const settings = { ...defaults, ...userPrefs };
console.log(settings);
// { theme: 'light', fontSize: 18, showSidebar: false }Reading right-to-left: each spread is "merged in on top of" what came before. Adding inline keys works the same way:
const settings = { ...defaults, ...userPrefs, theme: "dark" };
// theme is forced to 'dark' regardless of either sourceAdding or overriding one field
Spread + one extra property is the immutable way to update a record — central to Redux-style state updates, React state, and any pattern that avoids mutation.
const user = { id: 1, name: "Ada", age: 36 };
// "Update" — really, build a new object
const olderUser = { ...user, age: user.age + 1 };
console.log(user.age, olderUser.age); // 36 37Removing a property without mutating
Combine rest in destructuring with discarding the named field. The rest object is a new copy without that key — the original is untouched.
const user = { id: 42, name: "Ada", password: "hunter2" };
const { password, ...safeUser } = user;
console.log(safeUser); // { id: 42, name: 'Ada' }
console.log(user); // password still thereRest parameters in functions
Rest is not only for destructuring. In a function signature, ...args collects the remaining arguments into a real array — replacing the old arguments object.
function sum(label, ...nums) {
console.log(label, "got", nums.length, "numbers");
return nums.reduce((a, b) => a + b, 0);
}
console.log(sum("primes", 2, 3, 5, 7, 11));primes got 5 numbers 28
Spread vs Object.assign
The two are mostly interchangeable for shallow merging. The differences:
{ ...a, ...b }always returns a new object.Object.assign(target, ...)mutatestargetand returns it.Spread is purely syntax — it cannot be polyfilled in older environments without a transpiler.
Object.assigntriggers setters on the target; spread does not (it creates fresh data properties).
// Same result, different intent
const merged1 = { ...defaults, ...userPrefs };
const merged2 = Object.assign({}, defaults, userPrefs);Order matters — twice
Two subtle ordering points trip people up:
Inside an object literal, properties are evaluated left to right — later assignments overwrite earlier ones.
In a destructuring pattern,
...restmust come last — anything after it is a syntax error.
// Explicit key after spread — overrides
const a = { ...{ x: 1 }, x: 2 }; // { x: 2 }
// Spread after explicit key — overwrites your earlier value
const b = { x: 1, ...{ x: 99 } }; // { x: 99 }What spread does not do
It does not copy non-enumerable properties — most user-defined props are enumerable, so this rarely matters.
It does not copy the prototype. The result is always a plain object.
It does not copy property descriptors (getters/setters become plain data properties). Use
Object.definePropertiesif that matters.