Destructuring
Mental model: Destructuring is a pattern that mirrors the shape of the value, read right to left.
Level: beginner · about 12 minutes
const user = { name: 'Ada', role: 'engineer', id: 7 };
const { name, role } = user;
console.log(name, role); // 'Ada' 'engineer'
const [first, second] = ['a', 'b', 'c'];
console.log(first, second); // 'a' 'b'The pattern on the left mirrors the value on the right.
Destructuring pulls values out of an object or array into variables. For objects it matches by key, so order is irrelevant. For arrays it matches by position, so order is everything.
Defaults and renaming
const settings = { theme: 'dark', lang: null };
const {
theme = 'light', // key exists, so the default is ignored
lang = 'en', // key exists and is null, so the default is STILL ignored
fontSize = 16, // key missing, so the default applies
theme: alias, // rename: same key, second variable
} = settings;
console.log(theme, lang, fontSize, alias); // 'dark' null 16 'dark'
Nested patterns, with a safety net
const order = { id: 1, customer: { name: 'Ada' } };
const { customer: { name } } = order;
console.log(name); // 'Ada'
// If `customer` might be missing, default the pattern itself:
const { shipping: { city = 'unknown' } = {} } = order;
console.log(city); // 'unknown' instead of a TypeError
Rest: take some, keep the remainder
const record = { id: 9, password: 'hunter2', name: 'Ada', role: 'admin' };
const { password, ...safe } = record;
console.log(safe); // { id: 9, name: 'Ada', role: 'admin' }
const [head, ...tail] = [1, 2, 3, 4];
console.log(head, tail); // 1 [2, 3, 4]
Swapping, without a temporary
let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1
In parameters: the options object
Positional, hard to call
function connect(host, port, secure) {
// connect('db', undefined, true)
}
Destructured, self documenting
function connect({ host, port = 5432, secure = false } = {}) {
return `${secure ? 'https' : 'http'}://${host}:${port}`;
}
connect({ host: 'db', secure: true });The trailing = {} matters: without it, calling connect() with no argument throws, because you cannot destructure undefined.
From function returns
function parseRange(text) {
const [from, to] = text.split('-').map(Number);
return { from, to, span: to - from };
}
const { from, span } = parseRange('10-25');
console.log(from, span); // 10 15
| Pattern | Means |
|---|---|
const { a } = o | variable a from key a |
const { a: b } = o | variable b from key a |
const { a = 1 } = o | default when a is missing or undefined |
const { a: { b } } = o | nested read, a is a key not a variable |
const { a, ...rest } = o | rest is a new object without a |
const [, second] = arr | skip position 0 with a hole |
const { a = 5, b = 5 } = { a: null, b: undefined };
console.log(a, b);a is present with the value null, which is a real value, so the default is skipped. b is explicitly undefined, which is exactly when a default applies, so b becomes 5.
Try it yourself
Match the shape
const payload = {
id: 42,
profile: { name: 'Ada', address: { city: 'London' } },
tags: ['maths', 'engines', 'notes'],
};
const {
id,
profile: { name, address: { city, postcode = 'n/a' } },
tags: [primaryTag, ...otherTags],
} = payload;
console.log(id, name, city, postcode);
console.log(primaryTag, otherTags);
Delete address from the object and see which line breaks. Then add the default that fixes it.
Exercises
Strip the internal fields
Write stripInternals(record) which returns a copy of record without the password and _id properties. Use rest destructuring. The original object must not be modified.
Format a user safely
Write formatUser(user) returning "Name (City)". Missing name becomes "anonymous", missing city becomes "unknown", and a completely missing address or a missing argument must not throw. Solve it with destructuring defaults, not if statements.
Check yourself
- What does this log?
- 1 1 0 — The
= {}parameter default coversf(), thea = 1property default coversf({}), andf({ a: 0 })supplies a real value, so 0 wins because defaults only apply toundefined. - In
const { user: { id } } = data, which names become variables? - only
id—useris used as a key to look insidedata. Only the innermost name,id, is declared. If you want the intermediate object too, add it explicitly:const { user, user: { id } } = data. - Which line safely swaps two variables?
[a, b] = [b, a]— Array destructuring evaluates the right side first, building[b, a], then assigns positionally. No temporary variable is needed. The object version would match by key and assign each variable to itself.
Common mistakes
- Expecting a default to replace
null. Defaults only fire forundefined. - Forgetting
= {}on a destructured parameter, so calling with no argument throws aTypeError. - Assuming the outer key in a nested pattern becomes a variable. It does not.
Takeaways
- Objects destructure by key, arrays by position.
- Defaults apply to
undefinedonly, never tonull. - Rest in a pattern collects the remainder into a new object or array.
- Destructured parameters with defaults replace long positional argument lists.