Destructuring & Spread/Rest
Quick ways to pull values out of arrays/objects, and to expand or collect values using ...
What is it?
Reading a few properties out of an object, or the first couple of items from an array, usually meant writing const name = user.name; const age = user.age; one line at a time. Destructuring lets you pull multiple values out in a single line, matching the shape of what you're reading from. Its close relative, the spread/rest operator (...), lets you expand a collection into individual values, or collect several individual values back into one.
Explain like I'm 10
Destructuring is like unpacking a delivery box by naming exactly which items you want handed to you directly, instead of carrying the whole box around. Spread is like dumping the entire contents of one box into a bigger one; rest is the opposite — sweeping up 'everything else' into a single box of leftovers.
Examples
Destructuring objects and arrays
const user = { name: "Amara", age: 28, country: "Kenya" };
const { name, age } = user;
console.log(name, age); // "Amara" 28
const numbers = [10, 20, 30];
const [first, second] = numbers;
console.log(first, second); // 10 20
function greet({ name, age }) {
console.log(`Hi ${name}, age ${age}`);
}
greet(user); // "Hi Amara, age 28"Spread and rest
const base = { name: "Amara", age: 28 };
const withCountry = { ...base, country: "Kenya" }; // spread: expand base's properties in
function sum(...numbers) { // rest: collect all arguments into an array
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4)); // 10How it works
Destructuring is really just special syntax for a series of individual assignments, matched by object key or array position, done all at once. Spread (... used when producing a new value) copies each element/property out one at a time into the new array/object. Rest (... used in a parameter list or destructuring pattern) does the opposite — it gathers up any remaining values into a single array.
Why does it exist?
Both features exist to remove repetitive, line-by-line extraction and combination code. Destructuring makes "give me these specific pieces" a single readable line; spread/rest make "combine everything" or "gather the rest" equally short, especially common when copying objects/arrays immutably or writing flexible functions.
When to use it
Use destructuring anytime you only need a few named pieces out of an object or array — especially in function parameters. Use spread whenever you want to copy or merge objects/arrays without mutating the originals. Use rest whenever a function should accept any number of arguments, or you want "everything else" from an object/array.
When not to use it
Deeply nested destructuring patterns can become harder to read than a couple of plain property accesses — don't force it if it hurts clarity. And remember spread only makes a shallow copy — for nested objects, those nested values are still shared references, not fully cloned.
Common mistakes
Assuming spread makes a deep copy — it only copies one level; nested objects/arrays are still shared.
Mixing up destructuring order for arrays (position-based) with objects (name-based).
Forgetting that rest parameters must come last in a function's parameter list.
Practice exercises
- Easy:
Destructure
nameandemailout of auserobject in one line. - Medium:
Use spread to create a copy of an array with one extra item added, without mutating the original.
- Hard:
Write a function
sum(...numbers)using rest parameters that adds any number of arguments together.
Interview questions
What's the difference between destructuring an array and destructuring an object?
Array destructuring matches by position — const [a, b] = arr always takes the first two elements regardless of name; object destructuring matches by property name — const { x, y } = obj pulls out whichever keys are named, in any order.
What does this log? `const { count = 10 } = { count: undefined }; const { total = 10 } = { total: null }; console.log(count, total);`
10 then null — a destructuring default only applies when the corresponding value is exactly undefined (missing or explicitly undefined); any other falsy value, including null, is used as-is.
How do you rename a variable while destructuring an object?
const { name: userName } = user; extracts the name property but binds it locally as userName instead of name — useful for avoiding a naming collision or for a clearer local name.
How does nested destructuring work?
The pattern mirrors the shape of the data being pulled apart — const { address: { city } } = user; reaches into user.address and pulls out just its city property, without needing an intermediate const address = user.address; step.
What does this log? `const { id, ...details } = { id: 1, name: "Amara", age: 28 }; console.log(id, details);`
1 then { name: "Amara", age: 28 } — id is pulled out individually, and the rest pattern (...details) collects every remaining property into a new object.
What does this log? `let a = 1, b = 2; [a, b] = [b, a]; console.log(a, b);`
2 then 1 — the right side builds a temporary array [b, a] (i.e. [2, 1]) first, and array destructuring then assigns its elements back to a and b in order, swapping them without a manual temporary variable.
Why is destructuring especially useful in function parameters?
It lets a function declare exactly which named properties it needs from an object argument right in the signature — function greet({ name, age }) {} — making the function's dependencies visible at a glance instead of buried in user.name/user.age lookups inside the body.
Does spreading an object or array make a deep copy?
No — spread only copies one level. Any nested object or array inside is still the exact same reference in both the original and the copy, so mutating a nested value through the copy is visible through the original too.
If spread only makes a shallow copy, how would you actually deep-clone a value?
structuredClone(value) deep-clones most data (objects, arrays, dates, even circular references) natively; JSON.parse(JSON.stringify(value)) is an older alternative but silently drops functions and undefined, and throws on circular references.
What does spreading an array into a function call do?
It expands the array's elements into individual arguments — Math.max(...numbers) calls Math.max with each number as its own argument, which is otherwise impossible to do with an array in one call since Math.max doesn't accept an array directly.
What does this log? `const nums = [4, 2, 9, 1]; console.log(Math.max(...nums));`
9 — spreading nums passes 4, 2, 9, 1 as four separate arguments to Math.max, which then returns the largest of them.
What's the difference between rest parameters and the old `arguments` object?
A rest parameter (function f(...args)) collects extra arguments into a genuine array with every array method available; arguments is only array-like (it has indexes and length but no map/filter/etc.), and arrow functions don't have their own arguments at all, unlike rest parameters which work in any function.
What does this log? `function outer() { const inner = () => console.log(arguments[0]); inner(); } outer("hi");`
"hi" — the arrow function inner has no arguments object of its own, so referencing arguments inside it looks outward along the scope chain to outer's arguments, the same lexical lookup mechanism a closure uses for any other variable.
Where must a rest parameter appear in a function's parameter list?
Last — function f(a, ...rest) is valid, but function f(...rest, a) is a SyntaxError, because a rest parameter has to be able to greedily collect every remaining argument, which only makes sense as the final parameter.
How would you split an array into its first element and 'everything else' using destructuring?
const [first, ...rest] = arr; — first gets the element at index 0, and the rest pattern collects every remaining element into a new array called rest.
When merging two objects with spread, which properties win if both objects share a key?
Whichever object is spread later — { ...a, ...b } lets b's properties overwrite any matching keys from a, since each spread is applied left to right and later assignments to the same key simply replace earlier ones.
What's the difference between `Object.assign(target, source)` and object spread `{ ...source }`?
Object.assign mutates and returns its first argument, copying source's properties directly into it; spread always produces a brand-new object and leaves every input completely untouched — passing {} as Object.assign's target is what makes it behave non-destructively, like spread.
Can a destructuring pattern's default value reference another variable being destructured in the very same pattern?
No — each default expression can only see variables already established in an outer scope (or earlier parameters in a function signature); it can't reach across to a sibling property being destructured alongside it in the same pattern.