skip to content

In JavaScript destructuring, what does the rest element bind in `const [first, ...rest] = arr` and `const {id, ...others} = obj`, and what restrictions does the syntax place on it?

level: middleimportance: should knowfreq 60%

answer

  1. gathers on the left, scatters on the right
  2. always a container, never undefined
  3. nothing may follow it
  4. copies one level, like spread

basics

~20 s

A rest element collects everything not matched by earlier targets into a brand-new array or object — an empty one when nothing remains, never undefined. It must be the last element in the pattern, takes no default, and copies only one level deep.

solid answer

~40 s

In an array pattern, `...rest` drains the remaining values from the iterator into a fresh array; if the source had one element, `rest` is `[]`, not `undefined`. In an object pattern, `...others` copies the source's remaining own enumerable properties into a fresh object, which is the standard way to omit a key: `const {password, ...safe} = user` gives you everything but the password without mutating `user`. The restrictions are syntactic and enforced at parse time: the rest element must come last, a trailing comma after it is a SyntaxError, and it cannot carry a default. The copy is shallow, exactly like spread — nested objects inside `rest` or `others` are still shared with the source. Object rest is ES2018; array rest shipped with ES2015.

code

javascript · 11 lines
javascript
const [first, ...rest] = [1, 2, 3];
console.log(first, rest);              // 1 [2, 3]
console.log(([only, ...none] = [1], none)); // []

const user = { id: 7, name: 'ada', password: 'hunter2' };
const { password, ...safe } = user;
console.log(safe, Object.keys(user).length); // { id: 7, name: 'ada' } 3

const src = [{ n: 1 }, { n: 2 }];
const [, ...tail] = src;
console.log(tail[0] === src[1]);        // true — shallow

go deeper

for a junior

Know that ...rest in a pattern collects what is left over, that it must come last, and that const {password, ...safe} = user is how you build an object without one key.

for a middle

Explain that rest always binds a fresh container — [] or {} when nothing remains — that object rest takes own enumerable properties only, and that the same ... token means gather in a pattern and scatter in a literal.

for a senior

Use the omit idiom deliberately at trust boundaries — stripping secrets before logging or serializing — while recognizing that the result is shallow, so nested payloads still carry whatever they carried before.

for a principal

Decide whether ad-hoc field stripping at call sites is acceptable at all, or whether outbound shapes should be built by explicit allow-lists so a newly added sensitive field is excluded by default rather than by everyone remembering to destructure it away.

## Array rest: drains the iterator into a new array ```js const [first, ...rest] = [1, 2, 3]; // first = 1, rest = [2, 3] const [only, ...none] = [1]; // only = 1, none = [] const [, ...tail] = ['a', 'b', 'c']; // tail = ['b', 'c'] ``` Two properties matter. First, the binding is **always an array** — when the source is exhausted you get `[]`, never `undefined`, so `rest.length` and `rest.map(...)` are safe without a guard. Second, it is a **new** array: the source is not consumed or modified, and `rest` can be mutated freely without touching it. Because the values come from the iterator, holes in a sparse source are materialized: ```js const [...all] = [1, , 3]; // [1, undefined, 3] — dense, three real elements ``` The iterator visits every index up to `length` and produces `undefined` where no property exists, so the result has no holes. ## Object rest: the omit idiom ```js const user = { id: 7, name: 'ada', password: 'hunter2' }; const { password, ...safe } = user; // safe = { id: 7, name: 'ada' }; user is unchanged ``` Object rest copies the source's remaining **own enumerable** properties — string and symbol keys — into a fresh plain object. That makes it the idiomatic non-mutating "remove this key" operation, far cleaner than copying and then `delete`-ing. As with object spread, inherited and non-enumerable properties are not included, and getters on the source are invoked so the copy holds their computed values. A subtlety: the omitted key still creates a binding. `const { password, ...safe } = user` declares `password` in scope, which linters configured to reject unused variables will flag — most such rules have an option to ignore names consumed by a rest sibling. To omit a key whose name is computed, extract it under an alias: `const { [key]: _removed, ...others } = obj`. ## The syntactic rules All of these fail at parse time, before anything runs: ```js const [...head, last] = arr; // SyntaxError: rest element must be last const [a, ...rest, ] = arr; // SyntaxError: trailing comma after rest const [...rest = []] = arr; // SyntaxError: rest element may not have a default ``` The "must be last" rule follows from the mechanism: rest consumes everything remaining, so there is nothing left for a later target to match. "No default" follows from the guarantee that rest is always a container — the empty case is already covered by `[]` or `{}`, so a fallback would be meaningless. Only one rest element is allowed per pattern level, though nested patterns can each have their own: `const [{ id, ...restOfFirst }, ...restOfList] = items` is valid. ## Rest is shallow, like every other copy here ```js const src = [{ n: 1 }, { n: 2 }]; const [, ...tail] = src; tail[0] === src[1]; // true tail[0].n = 99; // src[1].n is now 99 ``` The container is new; the elements inside it are the same references. The same holds for object rest — `others.nested` is the source's nested object. Treat a rest binding exactly as you treat a spread copy. ## Rest versus spread — same three dots, opposite direction The token `...` means "gather" on the left of an assignment (in a binding pattern) and "scatter" on the right (in an array literal, object literal, or call argument list): ```js const [head, ...tail] = list; // rest: gathers remaining values const copy = [head, ...tail]; // spread: scatters them back ``` Being able to state that duality crisply — the same syntax, the position decides the meaning — is usually what the interviewer is checking, alongside the always-an-array guarantee and the last-position rule. ## Version notes Array rest in destructuring is part of ES2015. Object rest and object spread arrived later, in ES2018, which is why older code often uses a helper or `Object.assign` for the omit idiom instead.

  • Why can a rest element not have a default value?
    Because it is guaranteed to produce a container. When nothing remains, array rest binds `[]` and object rest binds `{}`, so there is no undefined case for a default to cover. The grammar rejects `const [...rest = []] = arr` outright rather than allowing a meaningless initializer.
  • How do you drop a property whose key is only known at runtime?
    Use a computed key with an alias to absorb it: `const { [keyName]: _omit, ...others } = obj`. The alias binding takes the unwanted value and `others` receives everything else. Note that the alias still occupies a name in scope, which is what lint rules about unused rest siblings are configured for.
  • Is the object produced by object rest safe to hand to code that might mutate it?
    Only at the top level. The rest object is fresh, so adding or deleting its own keys cannot affect the source, but every nested object or array inside it is the same reference the source holds. If the consumer may mutate nested data, copy those levels too or deep-copy before handing it over.

saying these in an interview costs you the question

  • Expects rest to be undefined when nothing remains
  • Puts the rest element before other targets
  • Tries to give a rest element a default
  • Thinks object rest deletes the key from the source
  • Assumes the rest object is a deep copy

context