JavaScript Destructuring & Spread Cheat Sheet
Covers array and object destructuring with defaults and renaming, the spread operator for arrays and objects, and common usage patterns.
Array Destructuring
Unpack array values into variables, skip items, and set defaults.
const [first, second, ...rest] = [1, 2, 3, 4, 5];console.log(first, second, rest); // 1 2 [3, 4, 5]// Skipping elementsconst [, , third] = [1, 2, 3];// Default valuesconst [a = 10, b = 20] = [undefined, 5];console.log(a, b); // 10 5// Swapping variableslet x = 1, y = 2;[x, y] = [y, x]; // x=2, y=1
Object Destructuring
Extract, rename, and default object properties, including nested ones.
const user = { name: 'Ana', age: 30, address: { city: 'Lima' } };const { name, age: years, address: { city } } = user; // rename + nestedconsole.log(name, years, city); // Ana 30 Limaconst { role = 'guest' } = user; // default when key is missingconst { name: n2, ...otherFields } = user; // rest pulls remaining keysfunction greet({ name, age = 18 } = {}) { // destructure params w/ default object console.log(`${name} is ${age}`);}
Spread Operator
Expand arrays, objects, and strings into new collections or arguments.
// Arraysconst nums = [1, 2, 3];const combined = [...nums, 4, 5]; // [1,2,3,4,5]const copy = [...nums]; // shallow copyconst max = Math.max(...nums); // spread into function args// Objects (ES2018+)const base = { a: 1, b: 2 };const merged = { ...base, c: 3, b: 20 }; // { a:1, b:20, c:3 } - later keys win// Strings spread into charactersconst chars = [...'hi']; // ['h', 'i']
Common Patterns
Where destructuring and spread show up most often in real code.
- Function parameter destructuring- Extract named args from a single options object: function f({a, b}) {}
- Swapping without a temp variable- [x, y] = [y, x] using array destructuring
- Importing named exports- import { useState, useEffect } from 'react' is object destructuring at the module level
- Rest in function args- function sum(...nums) collects remaining arguments into an array
- Nested defaults- const { a: { b = 1 } = {} } = obj guards against a being undefined
- Shallow copy caveat- Both spread and destructuring rest only shallow-copy; nested objects are still shared references
Computed Property Names in Destructuring
Pull a value out using a dynamic key name, not just a literal identifier.
const key = 'role';const user = { name: 'Ana', role: 'admin' };const { [key]: userRole } = user; // computed key requires a rename targetconsole.log(userRole); // 'admin'// Useful for normalizing dynamic API payloadsfunction pluck(obj, dynamicKey) { const { [dynamicKey]: value, ...rest } = obj; return { value, rest };}
Immutable Update Patterns (Reducer Style)
Combine destructuring and spread to update deeply nested state without mutation.
function reducer(state, action) { switch (action.type) { case 'UPDATE_USER': return { ...state, user: { ...state.user, ...action.payload }, // shallow-merge nested object }; case 'ADD_ITEM': return { ...state, items: [...state.items, action.item] }; // append immutably case 'REMOVE_ITEM': { const { [action.id]: removed, ...remainingById } = state.byId; // drop a key immutably return { ...state, byId: remainingById }; } case 'UPDATE_NESTED_ARRAY_ITEM': return { ...state, items: state.items.map(it => it.id === action.id ? { ...it, ...action.changes } : it), }; default: return state; }}
Destructuring Map, Set, and Other Iterables
Array destructuring works on anything iterable, not just arrays - including Map entries and generators.
const map = new Map([['a', 1], ['b', 2]]);for (const [key, value] of map) { // destructure Map entries directly console.log(key, value);}const [firstEntry] = map; // take just the first [key, value] pairconst set = new Set([1, 2, 3]);const [firstItem, ...restItems] = set; // Set is iterable, spreads into an array// Swapping Map values without a temp variablelet [a, b] = [map.get('a'), map.get('b')];[a, b] = [b, a];
Destructuring Generator Output
Consume only as much of a lazy generator as destructuring requests.
function* naturals() { let n = 1; while (true) yield n++; // infinite generator}const [first, second, third] = naturals(); // only pulls 3 values, generator stays lazyconsole.log(first, second, third); // 1 2 3// Custom iterable objects also work with destructuring/spreadconst range = { from: 1, to: 3, [Symbol.iterator]() { let current = this.from, last = this.to; return { next: () => current <= last ? { value: current++, done: false } : { value: undefined, done: true } }; }};console.log([...range]); // [1, 2, 3]
Gotchas & Edge Cases
Subtleties that separate intro usage from production-ready usage.
- Object destructuring needs parens as a statement- ({ a, b } = obj); requires parens/semicolon so JS doesn't parse the leading { as a block statement
- Spread on non-iterables throws- [...obj] fails with TypeError unless obj implements Symbol.iterator; plain objects need {...obj} instead
- Rest must be last- ...rest in both array and object destructuring must be the final element/property, or it's a SyntaxError
- Object spread ignores prototype chain- { ...obj } only copies own enumerable properties, not inherited ones from the prototype
- Array-likes aren't spreadable- arguments-like objects or DOM NodeLists in older engines need Array.from() instead of spread if they lack Symbol.iterator
- Default values evaluate lazily- const { a = expensiveCall() } = obj only invokes expensiveCall() when a is actually undefined
- Spread copy is O(n)- Repeatedly spreading into a growing array/object in a loop is O(n^2) overall; prefer push()/Object.assign target accumulation for large datasets
Object spread ({...obj}) and Object.assign() only produce a shallow copy - nested objects/arrays are still shared by reference, so mutating a nested property affects both the original and the copy.