JavaScript Array Destructuring

Array destructuring is an ES6 syntax that lets you unpack values from an array into individual variables in a single, readable statement. Instead of writing const first = arr[0]; const second = arr[1]; line by line, you can write one expression that mirrors the shape of the array you’re pulling values out of. It’s used constantly in modern JavaScript — from React’s useState hook to looping over Map entries — so understanding it thoroughly will make a lot of code you read (and write) click into place.

Overview / How it works

Destructuring is really just a different way of writing an assignment. On the left side of =, instead of a single variable name, you write a pattern wrapped in square brackets, [a, b, c], that mirrors the structure of the array on the right. JavaScript walks through the right-hand value position by position and assigns each position’s value to the corresponding name in the pattern.

Crucially, array destructuring does not work by numeric indexing under the hood — it works through the iterable protocol. When the engine sees const [a, b] = someValue, it calls someValue[Symbol.iterator]() to get an iterator, then calls .next() once for each variable in the pattern, assigning the value from each result. This is why you can destructure not just arrays, but any iterable — strings, Sets, Maps, the arguments of a generator, and so on. Anything that is not iterable (like a plain object) will throw a TypeError if you try to array-destructure it.

Because position, not name, determines what gets assigned, array destructuring is ideal when order is meaningful (coordinates, RGB values, a function that returns a fixed-size tuple like [value, setValue]). Object destructuring, by contrast, matches by property name and is better when order doesn’t matter.

Syntax

const [var1, var2, ...rest] = arrayExpression;
let [varA, varB = defaultValue] = arrayExpression;
[existingVar1, existingVar2] = arrayExpression;
  • [var1, var2, ...] — a pattern on the left of = whose positions line up with the array’s elements.
  • const / let / var — declares new bindings; omit the keyword entirely (and wrap the statement, or at least the assignment, so it isn’t parsed as a block) to assign into variables that already exist.
  • = defaultValue — supplies a fallback used only when the corresponding element is undefined (missing or explicitly undefined).
  • ...rest — must be last in the pattern; collects all remaining elements into a new array.
  • Empty slots ([, , third]) — skip elements you don’t need without naming them.
  • Nested patterns ([[a, b], c]) — destructure arrays of arrays by nesting bracket patterns inside the outer pattern.

Examples

Example 1: Basic destructuring and skipping elements

const colors = ['red', 'green', 'blue'];
const [first, second, third] = colors;
console.log(first, second, third);

const [, , onlyThird] = colors;
console.log(onlyThird);

Output:

red green blue
blue

The first destructure pulls all three elements out by position. The second shows how to skip elements you don’t care about: the two leading commas act as placeholders for 'red' and 'green', leaving only the third element bound to onlyThird.

Example 2: Default values and the rest pattern

const scores = [95, 82];
const [math, science = 75, art = 60] = scores;
console.log(math, science, art);

const numbers = [10, 20, 30, 40, 50];
const [num1, num2, ...rest] = numbers;
console.log(num1, num2, rest);

Output:

95 82 60
10 20 [ 30, 40, 50 ]

scores only has two elements, so art has no corresponding value and falls back to its default, 60. science does have a value (82), so its default is ignored entirely. In the second statement, ...rest scoops up every element after the first two into a brand-new array — the original numbers array is untouched.

Example 3: Nested destructuring, variable swapping, and iterating a Map

let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a, b);

const matrix = [[1, 2], [3, 4]];
const [[a1, a2], [b1, b2]] = matrix;
console.log(a1, a2, b1, b2);

const inventory = new Map([['apples', 10], ['bananas', 5]]);
for (const [item, count] of inventory) {
  console.log(`${item}: ${count}`);
}

Output:

2 1
1 2 3 4
apples: 10
bananas: 5

The classic swap trick works because the right-hand side [b, a] is fully evaluated into a temporary array before any assignment happens, so you don’t need a manual temp variable anymore. The nested pattern [[a1, a2], [b1, b2]] matches the shape of matrix exactly, one bracket level per level of nesting. Finally, the for...of loop shows array destructuring in the wild: each entry yielded by iterating a Map is itself a two-element [key, value] array, so destructuring it directly in the loop header is by far the most common way to read map entries.

Under the hood: the iterator protocol

Because destructuring is powered by iteration rather than indexing, you can use it on anything implementing Symbol.iterator — not just real arrays. Here’s proof using a Set, which has no numeric indices at all:

const uniqueValues = new Set([100, 200, 300]);
const [firstValue, secondValue] = uniqueValues;
console.log(firstValue, secondValue);

Output:

100 200

Step by step, the engine: (1) evaluates the right-hand side, getting the Set instance; (2) calls uniqueValues[Symbol.iterator]() to obtain an iterator object; (3) calls .next() once for firstValue, unwrapping the returned { value, done } object and assigning value; (4) calls .next() again for secondValue; (5) stops — the iterator is never asked for a third value, and (unlike a for...of loop that runs to completion) it is simply abandoned. Rest elements (...rest) keep calling .next() until done is true, collecting each value into a fresh array.

Common Mistakes

Mistake 1: Assuming defaults trigger for any ’empty-ish’ value

Default values only kick in when the element is strictly undefined — not for null, 0, or ''. This trips people up constantly:

const settings = [null, 'dark'];
const [theme = 'light', mode = 'auto'] = settings;
console.log(theme, mode);

Output:

null dark

theme ends up as null, not 'light', because the array explicitly contains null at that position — the default only applies to a missing or undefined slot. If you want null to also fall back to a default, combine destructuring with the nullish coalescing operator instead:

const [rawTheme, mode = 'auto'] = settings;
const theme = rawTheme ?? 'light';
console.log(theme, mode);

Output:

light dark

Mistake 2: Destructuring past the end of the array silently produces undefined

Requesting more variables than the array has elements is not an error — the extra variables simply become undefined, which can hide bugs until much later in your code:

const [x, y, z] = [1, 2];
console.log(z);

Output:

undefined

If a missing value should have a sensible fallback rather than silently becoming undefined, give it a default explicitly:

const [x, y, z = 0] = [1, 2];
console.log(z);

Output:

0

One more pitfall worth knowing even without a runnable example: array destructuring requires the right-hand side to be iterable. Writing const [a] = { a: 1 }; looks tempting if you’ve mixed up array and object destructuring, but a plain object has no Symbol.iterator, so it throws TypeError: {(intermediate value)} is not iterable at runtime. If the value you’re unpacking has named properties rather than ordered positions, you want object destructuring (const { a } = { a: 1 }) instead.

Best Practices

  • Use array destructuring for fixed-size, order-meaningful data (coordinates, RGB triples, [value, setValue] pairs) and object destructuring for named, order-independent data.
  • Always give sensible defaults for elements that might legitimately be missing, especially when destructuring the result of a function that can return a short array.
  • Prefer const for destructured bindings you won’t reassign; only reach for let when you genuinely need to change one of the values afterward, as with the swap pattern.
  • Use the rest pattern (...rest) instead of slice() when you want ‘the first N items, then everything else’ — it’s clearer and avoids an extra method call.
  • Destructure directly in function parameters when a function’s array argument represents a small tuple — it documents the expected shape right in the signature.
  • Keep destructuring patterns shallow and readable; if you find yourself nesting three or four levels deep, consider restructuring the underlying data or destructuring in two steps instead.
  • When looping over a Map or Object.entries(), destructure the [key, value] pair directly in the for...of header rather than indexing into the entry manually.

Practice Exercises

  • Exercise 1: Given const point = [12, 7, 3]; representing x, y, z coordinates, use array destructuring in one line to create variables x, y, and z, then log a sentence like 'Point is at (12, 7, 3)'.
  • Exercise 2: Write a function parseCsvRow(row) that receives an array like ['Alice', '29', 'Berlin'], destructures it into name, age, and city (giving city a default of 'Unknown' in case it’s missing), and returns an object { name, age, city }.
  • Exercise 3: Given const readings = [72, 68, 75, 71, 69, 74];, use destructuring with the rest pattern to capture the first two readings as first and second, and the remaining readings as others. Then log how many elements are in others (expected output: 4).

Summary

  • Array destructuring unpacks values from any iterable (not just real arrays) into individual variables by position.
  • It’s powered by the iterator protocol: Symbol.iterator and repeated .next() calls, not by index access.
  • Use commas to skip elements, = defaultValue to supply fallbacks for undefined elements, and ...rest to gather the remaining elements into a new array.
  • Patterns can nest to match nested arrays, and destructuring assignment (without a declaration keyword) can swap or reassign existing variables in one line.
  • Defaults only apply to undefined, not null or other falsy values — use ?? alongside destructuring when you need to cover null too.
  • Destructuring past the end of an array yields undefined silently rather than throwing, so add defaults where a missing value would be a bug.