Destructuring is one of the most useful ES6 features — it lets you unpack values from arrays (and objects) into named variables in a single, expressive line. Combined with the spread operator (...) for expanding arrays and the rest pattern for collecting remaining elements, destructuring makes JavaScript code dramatically more readable and less repetitive. These patterns appear constantly in React, API data handling, function parameters, and state management.
Array Destructuring Patterns
| Pattern | Syntax | Effect |
|---|---|---|
| Basic | const [a, b] = [1, 2] |
a=1, b=2 |
| Skip elements | const [a, , c] = [1, 2, 3] |
a=1, c=3 — middle skipped |
| Default values | const [a=0, b=0] = [1] |
a=1, b=0 — default when undefined |
| Rest element | const [first, ...rest] = [1,2,3,4] |
first=1, rest=[2,3,4] |
| Swap variables | [a, b] = [b, a] |
Swap without temp variable |
| Nested | const [[a, b], [c, d]] = [[1,2],[3,4]] |
Unpack nested arrays |
| Function return | const [x, y] = getCoords() |
Unpack multiple return values |
Spread Operator Uses
| Use Case | Syntax | Example |
|---|---|---|
| Copy array | [...arr] |
Shallow clone |
| Merge arrays | [...a, ...b] |
Concatenation without concat() |
| Insert items | [...a, newItem, ...b] |
Insert in middle |
| Spread to function | Math.max(...arr) |
Pass array as individual args |
| String to chars | [...'hello'] |
['h','e','l','l','o'] |
| Set to array | [...new Set(arr)] |
Deduplicate array |
Rest vs Spread
| Syntax | Role | Where Used |
|---|---|---|
...rest (rest) |
Collects multiple items into one array | Destructuring patterns, function parameters |
...spread (spread) |
Expands one array into individual items | Array literals, function call arguments |
const [first, ...rest] = arr is valid; const [...rest, last] = arr is a SyntaxError. This mirrors the rest parameter rule in function signatures. The rest element collects whatever is left after the named elements are assigned.const [status = 200, message = 'OK', data = []] = apiResponse. Each variable gets the API value when present, or a sensible default when the position is undefined. This is much cleaner than three separate null-check ternaries after the fact.const copy = [...original] creates a new array, but any objects inside are still shared references. Modifying copy[0].name also changes original[0].name. For deep cloning, use structuredClone(original).Basic Example
// ── Basic destructuring ───────────────────────────────────────────────────
const [red, green, blue] = [255, 128, 0];
console.log(red, green, blue); // 255 128 0
// ── Skip elements ─────────────────────────────────────────────────────────
const [first, , third, , fifth = 99] = [10, 20, 30, 40];
console.log(first, third, fifth); // 10 30 99 (fifth defaults to 99)
// ── Swap variables — no temp needed ──────────────────────────────────────
let x = 5, y = 10;
[x, y] = [y, x];
console.log(x, y); // 10 5
// ── Rest element ──────────────────────────────────────────────────────────
const [head, ...tail] = [1, 2, 3, 4, 5];
console.log(head); // 1
console.log(tail); // [2,3,4,5]
// ── Function returning multiple values ───────────────────────────────────
function getMinMax(numbers) {
const sorted = [...numbers].sort((a, b) => a - b);
return [sorted[0], sorted.at(-1)];
}
const [min, max] = getMinMax([5, 1, 8, 3, 9, 2]);
console.log(min, max); // 1 9
// ── Nested array destructuring ────────────────────────────────────────────
const matrix = [[1, 2], [3, 4], [5, 6]];
const [[a, b], [c, d]] = matrix;
console.log(a, b, c, d); // 1 2 3 4
// ── Spread — copy and merge ───────────────────────────────────────────────
const original = [1, 2, 3];
const copy = [...original]; // shallow clone
const extended = [...original, 4, 5]; // [1,2,3,4,5]
const prepended = [0, ...original]; // [0,1,2,3]
original.push(99);
console.log(original); // [1,2,3,99] — changed
console.log(copy); // [1,2,3] — independent copy
// ── Spread to function arguments ─────────────────────────────────────────
const nums = [3, 1, 4, 1, 5, 9, 2, 6];
console.log(Math.max(...nums)); // 9
console.log(Math.min(...nums)); // 1
// ── Deduplication with Set + spread ──────────────────────────────────────
const withDupes = ['css', 'js', 'html', 'css', 'js', 'react'];
const unique = [...new Set(withDupes)];
console.log(unique); // ['css','js','html','react']
// ── Destructuring in for...of ─────────────────────────────────────────────
const pairs = [['alice', 30], ['bob', 25], ['carol', 35]];
for (const [name, age] of pairs) {
console.log(`${name} is ${age}`);
}
// alice is 30 / bob is 25 / carol is 35
// ── Destructuring entries() ───────────────────────────────────────────────
const fruits = ['apple', 'banana', 'cherry'];
for (const [index, fruit] of fruits.entries()) {
console.log(`${index + 1}. ${fruit}`);
}
How It Works
Step 1 — Destructuring Matches by Position
Array destructuring assigns values by position, not by name. The first variable gets index 0, the second gets index 1, and so on. Empty commas [a,,c] skip positions — the second element is evaluated but discarded. If the array is shorter than the pattern, missing positions resolve to undefined (or the default value if provided).
Step 2 — Defaults Activate on undefined Only
Just like function default parameters, destructuring defaults activate when the value at that position is undefined — not for null, 0, false, or ''. This mirrors the behaviour of ?? but through the assignment syntax rather than an operator.
Step 3 — Variable Swap Without Temp
[a, b] = [b, a] creates a temporary array [b, a] on the right side and immediately destructures it into a and b. JavaScript evaluates the right side completely before assigning — so both original values are captured before either variable is overwritten.
Step 4 — Spread Calls the Iterable Protocol
...arr in an array literal or function call invokes the same iterable protocol as for...of. This means spread works on any iterable — arrays, strings, Sets, Maps, NodeLists, generator results. [...'hello'] produces ['h','e','l','l','o'] because strings are iterable character-by-character.
Step 5 — structuredClone for Deep Copies
When you need a fully independent copy where nested objects are also cloned, use structuredClone(arr) (ES2022). It handles nested objects, arrays, Maps, Sets, and Dates — things that JSON.parse(JSON.stringify(arr)) cannot handle (functions, undefined, circular references).
Real-World Example: CSV Parser and Coordinate System
// Parsing CSV rows with destructuring
function parseUserCSV(csvRow) {
const [id, name, email, role = 'viewer', active = 'true'] = csvRow.split(',').map(s => s.trim());
return {
id: Number(id),
name,
email,
role,
active: active === 'true',
};
}
const rows = [
'1, Alice Smith, alice@example.com, admin, true',
'2, Bob Jones, bob@example.com, editor',
'3, Carol White, carol@example.com',
];
const users = rows.map(parseUserCSV);
console.log(users);
// [
// { id:1, name:'Alice Smith', email:'alice@example.com', role:'admin', active:true },
// { id:2, name:'Bob Jones', email:'bob@example.com', role:'editor', active:true },
// { id:3, name:'Carol White', email:'carol@example.com', role:'viewer', active:true },
// ]
// Vector math with destructuring
function addVectors([x1, y1], [x2, y2]) { return [x1 + x2, y1 + y2]; }
function scaleVector([x, y], scalar) { return [x * scalar, y * scalar]; }
function dotProduct([x1, y1], [x2, y2]) { return x1 * x2 + y1 * y2; }
function magnitude([x, y]) { return Math.sqrt(x ** 2 + y ** 2); }
const v1 = [3, 4];
const v2 = [1, 2];
const [rx, ry] = addVectors(v1, v2);
console.log(`Sum: [${rx}, ${ry}]`); // Sum: [4, 6]
console.log(`Magnitude of v1: ${magnitude(v1)}`); // 5
console.log(`Dot product: ${dotProduct(v1, v2)}`); // 11
// Immutable state updates with spread
const state = { items: ['a', 'b', 'c'], selected: 0 };
// Add item — immutable
const afterAdd = { ...state, items: [...state.items, 'd'] };
// Remove item at index 1 — immutable
const afterRemove = {
...state,
items: state.items.filter((_, i) => i !== 1),
};
// Replace item at index 0 — immutable
const afterUpdate = {
...state,
items: state.items.map((item, i) => i === 0 ? 'A' : item),
};
console.log(state.items); // ['a','b','c'] — original unchanged
console.log(afterAdd.items); // ['a','b','c','d']
console.log(afterRemove.items); // ['a','c']
console.log(afterUpdate.items); // ['A','b','c']
Common Mistakes
Mistake 1 — Rest element is not last
❌ Wrong — rest must be the final element:
const [...rest, last] = [1, 2, 3]; // SyntaxError
✅ Correct — rest goes at the end:
const [first, ...rest] = [1, 2, 3]; // first=1, rest=[2,3]
Mistake 2 — Spread creates shallow copy — shared nested references
❌ Wrong — nested objects are still shared:
const original = [{ name: 'Alice' }, { name: 'Bob' }];
const copy = [...original];
copy[0].name = 'Carol';
console.log(original[0].name); // 'Carol' — original mutated!
✅ Correct — use structuredClone for deep copies:
const copy = structuredClone(original);
copy[0].name = 'Carol';
console.log(original[0].name); // 'Alice' — untouched
Mistake 3 — Destructuring undefined throws TypeError
❌ Wrong — trying to destructure undefined or null throws:
const [a, b] = undefined; // TypeError: undefined is not iterable
✅ Correct — provide a fallback with nullish coalescing:
const [a, b] = apiResponse ?? []; // safe — falls back to empty array
Quick Reference
| Pattern | Syntax | Result |
|---|---|---|
| Basic | const [a, b] = arr |
First two elements |
| Skip | const [a, , c] = arr |
1st and 3rd elements |
| Default | const [a = 0] = arr |
0 if arr[0] is undefined |
| Rest | const [first, ...rest] = arr |
first = arr[0], rest = arr.slice(1) |
| Swap | [a, b] = [b, a] |
Variables swapped |
| Shallow copy | [...arr] |
New array, shared nested refs |
| Deep copy | structuredClone(arr) |
Fully independent clone |
| Deduplicate | [...new Set(arr)] |
Unique values only |