Destructuring & Spread
Destructuring and the spread/rest syntax are concise patterns introduced in ES6 that eliminate a lot of repetitive property access and array indexing. They show up constantly in modern JavaScript and TypeScript.
Array Destructuring
const rgb = [255, 128, 0];
// Without destructuring
const red = rgb[0];
const green = rgb[1];
// With destructuring
const [r, g, b] = rgb;
// Skip elements with a hole
const [first, , third] = [10, 20, 30]; // first=10, third=30
// Swap values without a temp variable
let x = 1, y = 2;
[x, y] = [y, x]; // x=2, y=1
Object Destructuring
const user = { id: 1, name: "Alice", role: "admin" };
// Basic
const { name, role } = user;
// Rename: `role` becomes `userRole` locally
const { name: userName, role: userRole } = user;
// Default values — used only when the property is undefined
const { name: n, age = 25 } = user; // age defaults to 25
Destructuring in Function Parameters
One of the most common uses — it makes function signatures self-documenting:
interface Config {
host: string;
port?: number;
secure?: boolean;
}
function connect({ host, port = 443, secure = true }: Config): string {
return `${secure ? "https" : "http"}://${host}:${port}`;
}
connect({ host: "example.com" });
// "https://example.com:443"
TIP
When a function takes more than two or three arguments, consider accepting a single options object and destructuring it. This makes call sites more readable and arguments order-independent.
Rest in Destructuring
The rest element (...name) collects everything not already extracted:
// Array rest — must be last
const [head, ...tail] = [1, 2, 3, 4];
// head=1, tail=[2, 3, 4]
// Object rest — collects remaining own enumerable properties
const { id, ...rest } = { id: 1, name: "Alice", role: "admin" };
// id=1, rest={ name: "Alice", role: "admin" }
A common pattern is to use object rest to strip specific keys without mutation:
function omit<T extends object, K extends keyof T>(
obj: T,
...keys: K[]
): Omit<T, K> {
const { ...copy } = obj;
for (const key of keys) delete (copy as Record<string, unknown>)[key as string];
return copy as Omit<T, K>;
}
Spread
The spread operator (...) expands an iterable into individual elements. It is the inverse of rest.
// Spread into function arguments
const nums = [1, 2, 3];
Math.max(...nums); // same as Math.max(1, 2, 3) → 3
// Spread to copy/merge arrays (non-mutating)
const combined = [...nums, 4, 5]; // [1, 2, 3, 4, 5]
const copy = [...nums]; // shallow copy
// Spread to copy/merge objects (non-mutating)
const defaults = { theme: "dark", lang: "en" };
const userPrefs = { lang: "fr", fontSize: 14 };
const merged = { ...defaults, ...userPrefs };
// { theme: "dark", lang: "fr", fontSize: 14 }
// later spreads win on duplicate keys
WARNING
Spread creates a shallow copy. Nested objects and arrays are still shared by reference between the original and the copy.
Nested Destructuring
You can destructure as deeply as the data goes:
const response = {
status: 200,
data: {
user: { id: 42, name: "Bob" },
permissions: ["read", "write"],
},
};
const {
data: {
user: { name },
permissions: [firstPerm],
},
} = response;
// name = "Bob", firstPerm = "read"
NOTE
Deep destructuring in one line can become hard to read. If the structure is complex, consider intermediate variables.
Further Learning
Search these terms to go deeper:
- “MDN: Destructuring assignment” — comprehensive reference with all edge cases
- “ES6 destructuring patterns” on Axel Rauschmayer’s exploringjs.com — the most thorough free resource
- “JavaScript spread vs rest” — many good short articles clarifying the same syntax in different positions
- “Object rest spread proposal TC39” — useful background on how this landed in the spec
- “TypeScript destructuring types” — how TypeScript infers and annotates destructured bindings