JavaScript · Arrays & Iteration

JavaScript Array Destructuring & Spread — Unpacking, Copying and Combining Arrays

6 min read Updated 2026-06-18

Practice Array Destructuring & Spread interview questions

Two sides of the same syntax

ES2015 introduced two features that transformed how we work with arrays: destructuring, which unpacks values out of an array into variables, and the spread operator, which expands an array into individual elements. They share the ... token for the rest/spread roles and together make array manipulation dramatically more concise. This guide covers both, their interaction, and the patterns and pitfalls that matter day to day.

Array destructuring basics

Destructuring unpacks values by position using a pattern on the left of =.

const [first, second] = [10, 20]
first    // 10
second   // 20 assigned by index, not by name

It works on any iterable — arrays, strings, Sets, Maps, generators — not just arrays. That generality is why it's so widely useful.

Skipping, defaults, and swapping

You can skip positions with empty commas, supply defaults for missing values, and swap variables with no temp.

const [, , third] = ['a', 'b', 'c']   // skip first two -> 'c'

const [a = 1, b = 2] = [10]           // a=10, b=2 (default for missing)
const [c = 5] = [null]                // c=null default only for undefined

let x = 1, y = 2
;[x, y] = [y, x]                       // swap without a temp

Two subtleties: defaults apply only when the value is undefined (not null or other falsy values), and the swap line should start with a semicolon if the previous statement didn't end with one — otherwise [ is parsed as indexing.

Nested patterns and rest

Patterns can mirror nested structure, and a trailing rest element (...name) collects the remaining values into a new array.

const [[a, b], [c]] = [[1, 2], [3]]   // nested -> a=1, b=2, c=3

const [head, ...tail] = [1, 2, 3, 4]
head   // 1
tail   // [2, 3, 4] a real array

The rest element must be lastconst [...rest, last] is a SyntaxError. It always produces an array, even an empty one.

Destructuring in parameters and loops

Two of the most useful places for array destructuring are function parameters and for...of loops — especially with entries() and Object.entries.

function distance([x1, y1], [x2, y2]) {
  return Math.hypot(x2 - x1, y2 - y1)
}
distance([0, 0], [3, 4])   // 5

for (const [i, value] of arr.entries()) {
  console.log(i, value)    // index and value, cleanly
}
for (const [key, val] of Object.entries(obj)) { /* ... */ }

This keeps loop bodies free of clumsy pair[0]/pair[1] indexing.

The spread operator

Spread expands an iterable into individual elements — the inverse of a rest element. Its most common use is making a shallow copy or merging arrays.

const copy = [...arr]                 // shallow copy
const merged = [...a, ...b, ...c]     // concatenate
const withExtras = [0, ...a, 99]      // insert around elements

Element order follows source order, and you can freely interleave individual values with spreads. It reads more clearly than a.concat(b, c), though concat can be marginally faster for very large arrays.

Spread vs concat

Both merge arrays, but they have slightly different strengths. concat can append non-array values directly and is often faster on huge arrays; spread is more readable and supports interleaving.

[1, 2].concat(3, [4, 5])   // [1, 2, 3, 4, 5] — mixes values and arrays
[...[1, 2], 3, ...[4, 5]]  // same result via spread

Use spread for clarity in normal code; reach for concat in performance-critical paths over large arrays.

Spreading iterables and the emoji trap

Spread works on anything iterable: strings, Sets, Maps, arguments, NodeLists, generators. For splitting a string into characters, prefer spread over split('') because spread iterates by code point, correctly handling emoji and other astral characters.

[...'a😀b']        // ['a', '😀', 'b'] ✅
'a😀b'.split('')   // ['a', '\ud83d', '\ude00', 'b'] ❌ broken surrogate pair
[...new Set([1, 1, 2])]   // [1, 2] — dedupe in one line

Spreading a Set into an array is the idiomatic one-line way to deduplicate.

Passing arrays as arguments

Spread replaces the old Function.prototype.apply pattern for passing an array as separate arguments.

const nums = [5, 1, 9]
Math.max(...nums)    // 9
Math.max.apply(null, nums)   // old, verbose equivalent

One caveat: spreading an extremely large array (100k+ elements) into a call can exceed argument-count limits — use a loop or reduce for those cases.

The shallow-copy pitfall

The most common spread bug is assuming [...arr] deep-copies. It doesn't — nested objects/arrays remain shared.

const matrix = [[1], [2]]
const copy = [...matrix]
copy[0].push(99)
matrix[0]   // [1, 99] inner array was shared

For one level of nesting, map and spread each row (matrix.map(row => [...row])); for arbitrary depth, use structuredClone(matrix).

Immutable updates with spread

Spread is the backbone of immutable array updates — building a new array around the change instead of mutating.

const added    = [...arr, item]                            // append
const prepended = [item, ...arr]                           // prepend
const removed  = [...arr.slice(0, i), ...arr.slice(i + 1)] // remove at index

These are the standard patterns in React/Redux state. The ES2023 with and toSpliced methods cover replace/remove even more concisely.

A word on infinite iterables

Because destructuring pulls only as many values as the pattern needs, it works on infinite iterables — but a rest element would try to drain them and hang forever.

function* naturals() { let n = 1; while (true) yield n++ }
const [a, b, c] = naturals()     // 1, 2, 3 only three pulled
// const [...all] = naturals()   // infinite loop — never do this

Key takeaways

  • Array destructuring unpacks by position from any iterable; supports skipping, defaults (only for undefined), nested patterns, and a trailing rest element.
  • It shines in function parameters and for...of loops, especially with entries().
  • Spread expands iterables — use it to shallow-copy, merge, interleave, and pass arrays as arguments.
  • Prefer spread over split('') for characters (handles emoji) and [...new Set(arr)] to dedupe.
  • Spread copies are shallow — deep-update by copying the changed path or structuredClone.
  • Destructuring is safe on infinite iterables, but never use a rest element on one.

Destructuring and spread together let you express unpacking, copying, and combining arrays in a single readable line — fluency with them is a hallmark of idiomatic modern JavaScript.

Practice tests are coming soon

Get notified when interactive mock interviews and quizzes launch.