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
PatternMeans
const { a } = ovariable a from key a
const { a: b } = ovariable b from key a
const { a = 1 } = odefault when a is missing or undefined
const { a: { b } } = onested read, a is a key not a variable
const { a, ...rest } = orest is a new object without a
const [, second] = arrskip 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 covers f(), the a = 1 property default covers f({}), and f({ a: 0 }) supplies a real value, so 0 wins because defaults only apply to undefined.
In const { user: { id } } = data, which names become variables?
only id — user is used as a key to look inside data. 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 for undefined.
  • Forgetting = {} on a destructured parameter, so calling with no argument throws a TypeError.
  • 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 undefined only, never to null.
  • Rest in a pattern collects the remainder into a new object or array.
  • Destructured parameters with defaults replace long positional argument lists.