Skip to main content

Menu

System onlineAI Tutorials / 2026

AI Tutorials
Knowledge search

Recent · Popular · Tutorials

  • dart3 tutorials
  • java1 tutorial
  • Javascript3 tutorials
  • Node.js Introduction2 tutorials
  • dartdart · General · In depth
  • JavascriptJavascript · Advanced · In depth
  • JavascriptJavascript · Advanced · Mid
  • JavascriptJavascript · Advanced · Short

Find a tutorial

Tell the assistant what you want to learn. It suggests published chapters.

For example: “I want to learn Rust ownership from the start.” The conversation is kept only in this tab.

0 / 2,000 characters

Enter to send, Shift+Enter for a new line. Do not include personal information.

Chapter 10

Destructuring and Spread

Destructuring and spread look like two topics, but they share one core mechanism: **iterable** and **object** pattern matching against values at binding time. This chapter treats them as one system, because the bugs you'll meet in practice come from where they interact — defaults that only fire on `
Level: AdvancedLength: In depth4 min read

Read at another depth

Destructuring and Spread

Destructuring and spread look like two topics, but they share one core mechanism: iterable and object pattern matching against values at binding time. This chapter treats them as one system, because the bugs you'll meet in practice come from where they interact — defaults that only fire on undefined, holes in array patterns, and spread's silent deep-copy assumptions.

Prerequisites

This chapter assumes you are comfortable with the material through Iteration Methods: iteration protocols, rest parameters, Object.assign semantics, and the difference between binding and assignment. You should know that const destructuring still permits mutation of the bound object.

How Patterns Bind

A destructuring pattern is not syntax sugar evaluated at runtime after the fact; the spec compiles it into a sequence of Get/Next operations with defined failure modes. The key rule to internalize:

  • Defaults trigger on undefined only. null does not trigger a default; it binds as null.
  • Array patterns use the iterator protocol, so destructuring a Map yields entries, and destructuring a string yields code points.
  • Object patterns use Get on the target, so they work on any object (including exotic ones like Proxy), and they trigger getters.
Code example in javascript
const [a = 1, , b = 2] = [null, 'skipped'];
console.log(a, b); // null 2  — null does NOT take the default

const { x = 10 } = { x: undefined };
console.log(x); // 10
const { y = 10 } = Object.defineProperty({}, 'y', {
  get() { console.log('getter fired'); return 99; },
});
// logs "getter fired", y === 99

Renaming and computed keys

Code example in javascript
const { 'data-property': renamed, [dynamicKey]: alsoRenamed } = obj;

Computed property names in patterns are evaluated left-to-right; a computed key that evaluates to undefined is a TypeError at binding time, not a silent miss. This is a common crash when destructuring with a key pulled from user input.

Nested Patterns and Rest

Rest elements in array destructuring create a new array; rest properties in object destructuring create a new object that copies enumerable own properties, excluding the named ones. Object rest is the only "omit keys" operation in the language that preserves key order and never invokes the omitted keys' getters on the rest copy — but the original object's getters for included keys do fire.

Code example in javascript
const user = { id: 1, name: 'Ada', password: 'hunter2', toString() { return 'u'; } };
const { password, ...safe } = user;
// safe = { id, name, toString } — own enumerable only; symbols omitted

Note the failure mode: object rest silently drops symbol-keyed and non-enumerable properties. If you use rest for "strip sensitive fields," you've also stripped anything non-enumerable, which may or may not be what a downstream consumer expects. delete + structuredClone or an explicit allowlist is safer when the shape matters.

Spread Semantics and Copy Depth

Spread in object literals is CopyDataProperties: enumerable own properties, shallow. This is the single most common source of subtle bugs.

Code example in javascript
const state = { user: { name: 'Ada' }, tags: ['a'] };
const next = { ...state, user: { ...state.user } };
state.user.name = 'Grace';
console.log(next.user.name); // 'Ada' — safe only because we spread nested level
next.tags === state.tags;    // true — same array reference

Comparison of copy strategies:

TechniqueDepthPreserves prototypeHandles sparse arraysInvokes getters
`{...obj}` / `[...arr]`shallowno (Object.prototype)no (holes become `undefined`)yes (via Get)
`Object.assign`shallownon/ayes
`Object.create(proto)` + assignshallowyesn/ayes
`structuredClone`deepnoyesno (clones values)

Sparse arrays and length

Array spread calls the iterator, so holes become undefined and trailing holes beyond the last element are dropped. Object spread on an array copies length and numeric keys as data properties — you get an array-like, not an array. Destructuring, spread, Array.from, and slice handle holes differently; never assume uniformity across them.

Argument Passing: Spread vs Rest

The same three dots mean opposite things by position:

Code example in javascript
function f(...args) { return args; }   // rest: collects
const nums = [1, 2, 3];
f(...nums);                             // spread: expands
Math.max(...nums);                      // the idiomatic apply replacement

Function.prototype.apply(null, args) is now rarely needed, but note that spread with very large arrays (hundreds of thousands of elements) can exceed engine argument limits and throw. For unbounded arrays, iterate or use apply in chunks.

Also note: spread of a string spreads code points, so '👍'.length aside, [...'a👍b'] correctly yields three elements where index-based slicing would split the surrogate pair.

Choosing Between Patterns

Decision criteria:

  • Immutable-update style (reducers, config layering): object spread, but audit nesting depth. One level of spread protects nothing at depth two.
  • Omitting fields: rest destructuring, unless symbol/non-enumerable preservation matters.
  • Variadic APIs: rest parameters, never arguments (which is not a real array and is unavailable in arrows).
  • Deep copies: structuredClone, not nested spread — nested spread is O(n²) in depth and loses Date, Map, Set identity (spread on a Map produces a plain object).
Code example in javascript
// Spread on a Map is a trap:
const m = new Map([['a', 1]]);
console.log({ ...m });            // { } — no own enumerable props
console.log([...m]);              // [['a', 1]] — iterator works
console.log(new Map([...m, ['b', 2]])); // correct way to "extend" a Map

Iterables, not just arrays

Because array patterns and spread use the iterator protocol, they compose with generators and Set/Map for free:

Code example in javascript
function* ids() { yield 1; yield 2; yield 3; }
const [first, ...rest] = ids();
const unique = [...new Set([1, 1, 2, 3])]; // [1, 2, 3]

One caveat: spread consumes the iterator. Spreading the same generator twice yields [] the second time — the same retention trap as it.next() idempotence.

Common Gotchas Summary

  1. Defaults fire on undefined only; normalize null upstream or use ??.
  2. Object rest drops symbols and non-enumerables.
  3. Shallow spread leaves nested references shared.
  4. Computed pattern keys that are undefined throw.
  5. Spreading Map/Set into object literals yields empty or index-keyed objects.
  6. Iterators are single-use; spreading consumes them.

Exercise

You write const { mode = 'fast' } = config; and receive mode === null for some inputs. Why, and what are two fixes?

Show answer

Destructuring defaults fire only on undefined, not null. Fix by normalizing at the source, or change the binding to const { mode = 'fast' } = {}; const m = mode ?? 'fast'; (or bind then coalesce with ??).

Exercise

Your reducer returns { ...state, user: { ...state.user, name: action.name } } but a component reading state.user.prefs.theme still shows a stale value after prefs is replaced elsewhere. What is the mechanism?

Show answer

Spread is shallow. The outer spreads created new top-level objects, but prefs is still the same reference shared with the old state in whichever update replaced it without spreading that nesting level; the stale component is memoized against the old reference. Spread every level you replace, or use structuredClone/an immutable library for deep updates.

Exercise

When is object rest NOT equivalent to Object.assign({}, obj) followed by delete?

Show answer

Object rest copies enumerable own properties in one operation, excludes named keys, and does not invoke the omitted keys' getters; delete mutates the original (or requires a copy first), and any getters for deleted keys would already have fired during the copy. Rest also drops symbols and non-enumerables exactly like the copy — but it avoids ever calling getters for the excluded keys.

Spotted a mistake, or code that does not work?

Report an issue

Chapter: Destructuring and Spread

0 / 2,000 characters

Plain text, for example: the second code sample does not compile. Do not include personal information.

You can't do that