Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

JavaScript (ES2026)  Module 5 – Objects in depth

Destructuring objects and arrays

Unpack values from JavaScript objects and arrays with destructuring, with defaults, renaming, nested patterns, rest, swaps and clearer function parameters.

  • Beginner
  • 18 minutes
  • Examples run with Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free lesson, Pro answers: 18 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Extract values from objects and arrays with destructuring
  • Use defaults, renaming, nested patterns and rest elements
  • Write function parameters that destructure an options object for readable APIs

Before you start

What you’ll learn · Before you start

What you will learn

  • Extract values from objects and arrays with destructuring
  • Use defaults, renaming, nested patterns and rest elements
  • Write function parameters that destructure an options object for readable APIs

Before you start

On this page

Destructuring unpacks values from an object or an array into variables with a pattern that mirrors the data’s shape. const { city, temp } = weather creates the variables city and temp from the properties of the same names, and const [first, second] = queue takes the first two items by position. Patterns can rename ({ temp: celsius }), supply defaults ({ alerts = [] }), reach into nested objects ({ current: { humidity } }), skip items ([, second]) and collect the rest ({ ...others }, [head, ...tail]). They also work in function parameters, which turns an options object into readable named arguments. Three rules prevent most surprises: a default applies only when the value is undefined, never null; destructuring null or undefined, or reading inside a nested object that is missing, throws a TypeError; and array patterns accept any iterable. This lesson reads an API response, works through defaults, arrays, swaps, missing data and parameters, and ends with an address formatter.

Object patterns

Reading a weather API response with one pattern JavaScript · api-destructure.mjs
// Reading the fields you need from a weather API response with one destructuring pattern.
const response = JSON.parse(`{
  "city": "Pune",
  "current": { "temp": 31.4, "humidity": 62, "wind": { "speed": 11, "dir": "W" } },
  "units": "metric"
}`);

const {
  city, // the property city, into a variable called city
  current: { temp: celsius, humidity, wind: { speed } }, // nested, with temp renamed to celsius
  alerts = [], // missing from the response, so the default is used
  ...rest // every other own property: { units: 'metric' }
} = response;

console.log(city, celsius, humidity, speed, alerts, rest);

// A computed key reads the property named in a variable.
const field = 'humidity';
const { [field]: reading } = response.current;
console.log(`${field}: ${reading}`);

// The same reads without destructuring, for comparison.
const sameCity = response.city;
const sameCelsius = response.current.temp;
console.log(sameCity === city, sameCelsius === celsius);

Output

Pune 31.4 62 11 [] { units: 'metric' }
humidity: 62
true true

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node api-destructure.mjs

A response object, the destructuring pattern matched against it by key, and the five variables the pattern creates.the response objectcity: 'Pune'current: { temp: 31.4, humidity: 62 }units: 'metric'the pattern{ city, current: { temp: celsius, humidity }, alerts = [], ...rest }the variables it createscity = 'Pune'celsius = 31.4humidity = 62alerts = [] (the default)rest = { units: 'metric' }matched by keycreates

A destructuring pattern matched against a response

Text description of the diagram

The diagram has three boxes from top to bottom.

  1. The response object has `city: 'Pune'`, a nested `current` object with `temp: 31.4` and `humidity: 62`, and `units: 'metric'`.
  2. The pattern `{ city, current: { temp: celsius, humidity }, alerts = [], ...rest }` is matched against it by key.
  3. It creates five variables: `city` is `'Pune'`; `celsius` is `31.4`, read from `current.temp` and renamed; `humidity` is `62`; `alerts` is `[]`, its default, because the response has no `alerts`; and `rest` is `{ units: 'metric' }`, the properties no other part of the pattern named. No variable called `current` is created: the nested pattern only reads inside it.

Each part of the pattern does one job:

  • city reads response.city into a variable with the same name. This is the common case: the variable is named after the property.
  • current: { temp: celsius, humidity, wind: { speed } } is a nested pattern. The part before the colon names the property to read; the part after it says what to do with the value, here destructure it again. temp: celsius renames: it reads temp into a variable called celsius. No variable called current or wind is created.
  • alerts = [] is a default. The response has no alerts, so the variable gets an empty array instead of undefined.
  • ...rest collects every own property the pattern did not name into a new object, here { units: 'metric' }. It must come last.
  • { [field]: reading } uses a computed key, so the property to read can come from a variable.

The last line confirms that destructuring is only shorter syntax for property reads: city is exactly response.city. The variables are copies of the values, so for nested objects they hold references to the same objects, as with any assignment.

Defaults apply only to undefined

Defaults with null, missing and undefined values JavaScript · defaults.mjs
// A default applies only when the value is undefined: missing, or undefined itself.
const { qty = 1 } = { qty: null };
const { size = 'M' } = {};
const { colour = 'blue' } = { colour: undefined };
console.log(qty, size, colour);

// Defaults are evaluated only when they are needed, from left to right, and may use earlier names.
let lookups = 0;
function lookUpCity() {
  lookups += 1;
  return 'Mumbai';
}
const { city = lookUpCity(), label = `Weather in ${city}` } = { city: 'Pune' };
console.log(label, '| lookups:', lookups);

// When null also means "no value", ?? treats null and undefined alike.
const saved = { qty: null };
const { qty: fromPattern = 1 } = saved;
const fromNullish = saved.qty ?? 1;
console.log(fromPattern, fromNullish);

Output

null M blue
Weather in Pune | lookups: 0
null 1

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node defaults.mjs

The specification checks for one value: if the value read is undefined, the default is evaluated and used. { qty: null } therefore keeps null, while a missing size and an explicit undefined both get their defaults. Data from JSON is the usual trap, because JSON has null but no undefined: an API that sends "qty": null defeats a default of 1. When null should fall back as well, use ??, which treats null and undefined alike.

Defaults are expressions that run only when they are needed, from left to right. lookUpCity() never ran, because city had a value, and label could use city because it comes later in the pattern.

Array patterns

Positions, holes, rest, a swap and four kinds of iterable JavaScript · swap-and-rest.mjs
// Array patterns take values by position.
const queue = ['Asha', 'Ravi', 'Meera', 'John'];
const [first, second, ...waiting] = queue;
console.log(first, second, waiting);

const [, , third = 'nobody'] = ['A1', 'A2']; // skip two places; there is no third value, so the default is used
console.log(third);

// Swapping two variables needs no temporary variable.
let left = 'tea';
let right = 'coffee';
[left, right] = [right, left];
console.log(left, right);

// Any iterable works: a string, a Set, a Map, a generator.
const [initial] = 'Asha';
const [firstTag, secondTag] = new Set(['new', 'sale', 'new']);
const [[firstKey, firstValue]] = new Map([['pen', 20]]);
console.log(initial, firstTag, secondTag, firstKey, firstValue);

function* ticketNumbers() {
  let n = 1;
  while (true) yield `T${n++}`; // never ends on its own
}
const [t1, t2] = ticketNumbers(); // takes two values, then stops the generator
console.log(t1, t2);

Output

Asha Ravi [ 'Meera', 'John' ]
nobody
coffee tea
A new sale pen 20
T1 T2

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node swap-and-rest.mjs

Array patterns match by position instead of by name:

  • [first, second, ...waiting] takes the first two items and collects the remaining ones into a new array.
  • A hole, [, , third], skips a position. A default works as with objects: there is no third item, so third is 'nobody'.
  • [left, right] = [right, left] swaps two variables. It builds a temporary array on the right and destructures it on the left, so no third variable is needed.
  • An array pattern accepts any iterable, because it reads values with the iteration protocol: a string gives characters, a Set gives its values without duplicates, a Map gives [key, value] pairs (destructured again here), and a generator gives values on demand. The pattern takes only what it needs and then closes the iterator, which is why the endless ticketNumbers() generator is safe here.

Objects that are not iterable cannot be destructured with an array pattern: const [x] = {} throws a TypeError.

One syntax trap comes with destructuring assignment to existing variables. In code without semicolons, a line that starts with [ is joined to the line before it:

A swap after a line with no semicolon JavaScript · asi-swap.mjs
// Code written without semicolons: a line that starts with [ continues the line before it.
const broken = new Function(`
  let a = 1
  let b = 2
  const pair = [a, b]
  [a, b] = [b, a]
  return { a, b, pair }
`);
console.log('without a semicolon:', broken());

const fixed = new Function(`
  let a = 1
  let b = 2
  const pair = [a, b]
  ;[a, b] = [b, a]
  return { a, b, pair }
`);
console.log('with ;[ at the start:', fixed());

Output

without a semicolon: { a: 1, b: 2, pair: [ 2, 1 ] }
with ;[ at the start: { a: 2, b: 1, pair: [ 1, 2 ] }

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node asi-swap.mjs

Without the semicolon, const pair = [a, b] and the next line are read as const pair = [a, b][a, b] = [b, a], which assigns to an element of a temporary array: a and b stay as they were, pair becomes [2, 1], and no error appears. Starting the line with ;[ (or ending the previous one with ;) fixes it. An object pattern assigned to existing variables has its own rule: wrap the statement in parentheses, ({ a, b } = source), because a line that starts with { is read as a block.

When the data is missing

Destructuring reads properties, so it fails where a property read fails:

Missing and null nested objects JavaScript · nested-missing.mjs
// A nested pattern reads inside an inner object, so the inner object has to be there.
const withAddress = { name: 'Asha', address: { city: 'Pune', pin: '411001' } };
const withoutAddress = { name: 'Ravi' };
const nullAddress = { name: 'Meera', address: null };

const cityOf = ({ address: { city } }) => city;
console.log(cityOf(withAddress));
try {
  cityOf(withoutAddress);
} catch (error) {
  console.log('missing address:', error.name);
}

// A default for the inner object covers a missing one, but not null: defaults apply only to undefined.
const safeCityOf = ({ address: { city } = {} }) => city;
console.log(safeCityOf(withoutAddress));
try {
  safeCityOf(nullAddress);
} catch (error) {
  console.log('null address:', error.name);
}

// For data that may hold null, optional chaining with ?? is often the clearer tool.
const cityOrUnknown = (person) => person.address?.city ?? 'unknown';
console.log(cityOrUnknown(nullAddress), cityOrUnknown(withAddress));

// Destructuring null or undefined itself always throws.
try {
  const { name } = null;
  console.log(name);
} catch (error) {
  console.log('destructuring null:', error.name);
}

Output

Pune
missing address: TypeError
undefined
null address: TypeError
unknown Pune
destructuring null: TypeError

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node nested-missing.mjs

  • ({ address: { city } }) throws a TypeError for Ravi, who has no address: the nested pattern tries to read city from undefined.
  • A default for the nested object, { address: { city } = {} }, handles a missing address, and city is simply undefined.
  • That default does nothing for Meera, whose address is null, because defaults apply only to undefined. Optional chaining with ??, person.address?.city ?? 'unknown', handles both cases and is often the clearer choice for data that may hold null.
  • Destructuring null or undefined itself, const { name } = null, always throws.
JSON Formatter Format a real API response first to see which nested objects can be missing or null before you write the pattern.

Destructuring parameters

A function can destructure its argument directly in the parameter list. This is the usual way to write a function that takes an options object:

A ticket formatter with named, optional parameters JavaScript · params.mjs
// Destructuring in a parameter list: named options, defaults, and a call with no argument at all.
function formatTicket({ train, coach = 'GEN', seat, passenger: { name } = {} } = {}) {
  return `${train ?? 'train ?'} · coach ${coach} · seat ${seat ?? '-'} · ${name ?? 'no name'}`;
}

console.log(formatTicket({ train: '12127', coach: 'B2', seat: 14, passenger: { name: 'Asha' } }));
console.log(formatTicket({ seat: 3, train: '22105' })); // any order; missing options get their defaults
console.log(formatTicket()); // the final = {} makes even a call with no argument safe

// The positional version needs the right order, and a placeholder for every option that is skipped.
function formatTicketByPosition(train, coach = 'GEN', seat, name) {
  return `${train} · coach ${coach} · seat ${seat} · ${name}`;
}
console.log(formatTicketByPosition('12127', undefined, 14, 'Asha'));

// Destructuring in a loop: the [key, value] pairs of Object.entries.
const fares = { sleeper: 450, ac3: 1200 };
for (const [travelClass, fare] of Object.entries(fares)) console.log(`${travelClass}: ${fare}`);

Output

12127 · coach B2 · seat 14 · Asha
22105 · coach GEN · seat 3 · no name
train ? · coach GEN · seat - · no name
12127 · coach GEN · seat 14 · Asha
sleeper: 450
ac3: 1200

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node params.mjs

  • Callers pass named options in any order, { seat: 3, train: '22105' }, and every option that is left out gets its default or undefined. The positional version needs every argument in the right place and an undefined for each one that is skipped, which is hard to read and easy to get wrong.
  • The final = {} is the default for the whole argument. Without it, formatTicket() would try to destructure undefined and throw.
  • Nested defaults, passenger: { name } = {}, let callers leave out whole groups of options.
  • The same patterns work in loops: for (const [travelClass, fare] of Object.entries(fares)) names both parts of each entry.

Destructuring does not change the object it reads, and the variables it creates are independent of the object afterwards: assigning to city later does not change response.city.

Exercise: format an address that may have parts missing

Exercise · Easy · JavaScript

Format an address that may have parts missing

formatAddress(person) turns { name: 'Asha Rao', address: { line1: '12 MG Road', city: 'Pune', pin: '411001' } } into one line: 'Asha Rao, 12 MG Road, Pune 411001'. Real records are incomplete, and the version in format-address.mjs crashes or prints undefined for them. Use destructuring, with defaults where they help, so that it handles every case:

  • The line is the name, the first address line and "city pin", joined with ', '. The city and the pin are joined with a single space.
  • A part that is missing, undefined, null or the empty string '' is left out, together with its separator: { name: 'Ravi', address: { city: 'Kolkata' } } gives 'Ravi, Kolkata'.
  • A person with no address, or with address: null, gives just the name.
  • formatAddress() with no argument at all returns ''.

Starter code · format-address.mjs

// formatAddress(person): one line for an address whose parts may be missing (the exercise of lesson
// javascript/destructuring). This version works only for complete records.

export function formatAddress({ name, address: { line1, city, pin } }) {
  return `${name}, ${line1}, ${city} ${pin}`;
}
The sample tests · format-address.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { formatAddress } from './format-address.mjs';

test('a complete record', () => {
  const person = { name: 'Asha Rao', address: { line1: '12 MG Road', city: 'Pune', pin: '411001' } };
  assert.equal(formatAddress(person), 'Asha Rao, 12 MG Road, Pune 411001');
});

test('missing parts are left out with their separators', () => {
  assert.equal(formatAddress({ name: 'Ravi', address: { city: 'Kolkata' } }), 'Ravi, Kolkata');
  assert.equal(formatAddress({ name: 'Ravi', address: { line1: '4 Park Street', pin: '700016' } }), 'Ravi, 4 Park Street, 700016');
  assert.equal(formatAddress({ address: { line1: '4 Park Street', city: 'Kolkata' } }), '4 Park Street, Kolkata');
});

test('no address, or a null address, gives just the name', () => {
  assert.equal(formatAddress({ name: 'Meera' }), 'Meera');
  assert.equal(formatAddress({ name: 'Meera', address: null }), 'Meera');
});

test('null and empty strings count as missing', () => {
  assert.equal(formatAddress({ name: '', address: { line1: null, city: 'Pune', pin: '' } }), 'Pune');
  assert.equal(formatAddress({ name: 'John', address: { line1: '', city: '', pin: '560001' } }), 'John, 560001');
});

test('a call with no argument returns an empty string', () => {
  assert.equal(formatAddress(), '');
  assert.equal(formatAddress({}), '');
});

test('the record is not changed', () => {
  const person = { name: 'Asha', address: { city: 'Pune' } };
  formatAddress(person);
  assert.deepEqual(person, { name: 'Asha', address: { city: 'Pune' } });
});
A hint

Destructure the parameter with a default for the whole argument, ({ name, address } = {}), so a call with no argument works. A default for address itself would not help with address: null, because defaults apply only to undefined: destructure the inner object from address ?? {} instead. Then collect the parts in an array and keep only the ones that are present before you join them.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Self-check

Check yourself

6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 6 What does this program print?

    Read the code, then choose one answer.

    const { qty = 1, note = 'none' } = { qty: null, note: undefined };
    
    console.log(qty, note);
    Show the answer to question 1

    Answer: null none

    A default is used only when the value is undefined. note holds undefined, so it gets 'none'; qty holds null, which is a value, so it stays null. Use ?? 1 when null should also fall back.

  2. Question 2 of 6 What does this program print?

    Read the code, then choose one answer.

    const { a: x, ...others } = { a: 1, b: 2, c: 3 };
    
    console.log(x, others);
    Show the answer to question 2

    Answer: 1 { b: 2, c: 3 }

    a: x reads the property a into a variable called x; no variable a is created. The rest element collects the own properties that the pattern did not name into a new object.

  3. Question 3 of 6 What does this program print?

    Read the code, then choose one answer.

    const [first, , third = '?', ...rest] = 'hello';
    
    console.log(first, third, rest);
    Show the answer to question 3

    Answer: h l [ 'l', 'o' ]

    Array patterns work with any iterable, and a string yields its characters. The hole skips 'e', third gets 'l' (its default is not needed), and the rest element collects what is left.

  4. Question 4 of 6 const { address: { city } } = { name: 'Ravi' }; What happens?

    Choose one answer.

    Show the answer to question 4

    Answer: It throws a TypeError, because there is no address object to read city from

    A nested pattern reads city from the value of address, and that value is undefined. Give the nested pattern a default, { address: { city } = {} }, to cover a missing object; a null address still needs ??.

  5. Question 5 of 6 What does this program print?

    Read the code, then choose one answer.

    function label({ size = 'M', colour } = {}) {
      return `${size}-${colour}`;
    }
    
    console.log(label(), label({ colour: 'red' }));
    Show the answer to question 5

    Answer: M-undefined M-red

    The = {} after the pattern is the default for the whole argument, so label() destructures an empty object and gets size = 'M' and colour = undefined. Without it, label() would throw a TypeError.

  6. Question 6 of 6 In a file written without semicolons, why does ;[a, b] = [b, a] start with a semicolon?

    Choose one answer.

    Show the answer to question 6

    Answer: Without it, the line is joined to the line before, so [a, b] becomes a property access on the previous value

    Automatic semicolon insertion does not end a statement before a line that starts with [. The previous line, for example const pair = [a, b], would continue as [a, b][a, b] = [b, a], which assigns to an array element and swaps nothing.

Interview questions

Fresher · What is destructuring in JavaScript, and when does a default value in a pattern apply?

Destructuring is syntax that copies values out of an object or an array into variables with a pattern shaped like the data: const { name, city } = user reads two properties by name, and const [first, second] = list reads two items by position. Patterns can rename ({ city: town }), go into nested objects, skip array items with holes and collect the remaining values with a rest element such as ...others. A default value, as in { qty = 1 }, is used only when the value read is undefined, either because the property is missing or because it holds undefined. A null value is kept, which matters for JSON data, where missing values often arrive as null; use ?? when null should fall back too. Destructuring null or undefined itself, or a nested object that is missing, throws a TypeError.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    A service reads its settings with const { timeoutMs = 5000, retries = 3 } = config. After the settings moved to a JSON file written by an admin tool, every request started timing out at once. The tool writes "timeoutMs": null when the field is left empty. Explain the chain of events and make loading the settings robust.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A request handler starts with const { user: { id }, items: [firstItem] } = await res.json(). In production some requests fail with TypeErrors, and a teammate asks for "defensive destructuring". Which inputs make each part throw, why are defaults at every level not enough, and how would you rewrite it?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Object patterns read by name ({ city, temp: celsius }), array patterns by position ([first, , third]); both support defaults, nesting and a final rest element.
  • A default applies only when the value is undefined; null is kept, so use ?? for data that sends null.
  • Destructuring null or undefined, or reading inside a missing nested object, throws a TypeError; give nested patterns a default = {}, or use optional chaining for data that may be null.
  • Array patterns accept any iterable (strings, Sets, Maps, generators) and stop the iterator once they have what they need.
  • [a, b] = [b, a] swaps; in code without semicolons, start such a line with ;. Wrap an object assignment pattern in parentheses: ({ a } = obj).
  • Destructured parameters with a final = {} give functions readable, optional named options that callers can pass in any order.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.