Articles

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.

Takina Takina · · 5 min read
A code editor showing JavaScript component code

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.

Takina 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.

#JavaScript #Web Development #Frontend
Takina 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.

#JavaScript #Web Development #Frontend
Takina 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.

#Web Development #JavaScript #Frontend