JavaScript Destructuring: Arrays, Objects, Rest/Spread
JavaScript destructuring unpacks values from arrays and objects into variables in one expression. How it works, with the rest and spread operators.
Destructuring is JavaScript syntax that unpacks values out of arrays or properties out of objects and assigns them directly to variables, in one expression, instead of accessing them one at a time. It’s not a new capability — everything it does was already possible with index and property access — but it removes enough repetitive code that it’s become the default way to pull values out of function arguments, API responses, and imported modules.
Array destructuring
Array destructuring matches by position. The pattern on the left mirrors the shape of the array on the right:
const point = [10, 20];
const [x, y] = point;
// x === 10, y === 20
Skipping an element just means leaving a gap in the pattern:
const [, second, third] = [1, 2, 3];
// second === 2, third === 3
Because it matches by position, array destructuring is a natural fit for return values that come back as fixed-shape tuples, like a useState-style hook returning a value and a setter, or Object.entries() yielding [key, value] pairs to iterate over.
Object destructuring
Object destructuring matches by property name instead of position, so order doesn’t matter:
const user = { id: 1, name: "Alex", role: "admin" };
const { name, role } = user;
// name === "Alex", role === "admin"
You can rename a property while pulling it out, and give it a default value if the source might not have that key at all:
const { name: displayName, nickname = "Anonymous" } = user;
// displayName === "Alex", nickname === "Anonymous" (no `nickname` on user)
This combination — rename plus default — is why destructuring shows up so heavily in function signatures. A function that takes an options object can declare exactly which fields it wants, with sensible fallbacks, all in the parameter list itself:
function createUser({ name, role = "member", active = true } = {}) {
// ...
}
That trailing = {} matters: without it, calling createUser() with no argument at all throws, because you can’t destructure undefined. It’s a subtle gotcha worth remembering any time you destructure a function parameter that might be omitted.
Nested destructuring
Both forms nest, mirroring however deep the actual data structure goes:
const response = {
data: { user: { id: 1, name: "Alex" } },
meta: { page: 1 },
};
const {
data: {
user: { name },
},
} = response;
// name === "Alex"
This is common when destructuring the shape of a JSON API response directly in a function parameter, pulling out only the two or three fields a function actually needs from a much larger payload.
The rest operator: gathering what’s left
The rest operator (...) collects whatever destructuring didn’t explicitly pull out into a new array or object:
const [first, ...others] = [1, 2, 3, 4];
// first === 1, others === [2, 3, 4]
const { id, ...rest } = { id: 1, name: "Alex", role: "admin" };
// id === 1, rest === { name: "Alex", role: "admin" }
This is the idiomatic way to split an object into “the field I care about” and “everything else,” which comes up constantly when forwarding props in a UI component or stripping a sensitive field before logging an object.
The spread operator: the reverse operation
Spread uses the same ... syntax but does the opposite job — it expands an array or object out, rather than collecting values into one. It shows up most often when building a new array or object from an existing one without mutating the original:
const original = { id: 1, name: "Alex" };
const updated = { ...original, name: "Alexis" };
// updated === { id: 1, name: "Alexis" }, original unchanged
That “copy, then override” pattern is the backbone of immutable state updates in frameworks that expect you not to mutate state directly — spreading the previous state into a new object and overriding just the changed keys is the standard way to update state without breaking reference equality checks.
Spread also merges arrays and function arguments cleanly:
const combined = [...arrayA, ...arrayB];
sum(...numbers); // spreads an array into individual arguments
Rest and spread look identical — context tells them apart
The same three dots mean opposite things depending on which side of an assignment they’re on. On the left side of a destructuring pattern (or in a function’s parameter list), ... is rest — it gathers. On the right side of an assignment, or inside an array or object literal being built, ... is spread — it expands. There’s no separate keyword; the position is the only signal.
Where it fits with other JavaScript features
Destructuring composes naturally with optional chaining and nullish coalescing when the source object’s shape isn’t guaranteed — chain a ?. before destructuring a possibly-missing nested object, or default a destructured value with ?? instead of = when you specifically want to treat null the same as undefined. It’s also the standard way .map(), .filter(), and .reduce() callbacks unpack array elements that are themselves arrays or objects, and it pairs with const over let almost by convention — see var vs let vs const for why block-scoped bindings became the default.
TypeScript adds one more layer worth knowing: a destructured parameter can be typed with an inline object type or an interface, which is how most typed React components declare their props.
The takeaway
Destructuring unpacks array elements by position and object properties by name into individual variables, with support for renaming, defaults, and arbitrary nesting. The rest operator gathers whatever’s left over into a new array or object; the spread operator does the reverse, expanding a collection back out — most often to build an updated copy without mutating the original. Together they’ve replaced a large share of the manual property access and Object.assign() calls that used to clutter everyday JavaScript.
Tagged
Keep reading
Takina · · 5 min read Promise.all() vs allSettled() vs race() Compared
Promise.all() fails fast, allSettled() waits for every result, and race() returns whichever promise finishes first — how to choose correctly.
Takina · · 4 min read JavaScript Spread vs Rest Operators, Explained
The spread operator (...) expands an iterable into individual elements; the rest operator collects elements back into an array. Same syntax, opposite jobs.
Takina · · 4 min read ResizeObserver API Explained: Watching Element Size
The ResizeObserver API lets JavaScript watch an element's box size and react without polling or resize-event hacks. How it works and when to use it.