Module 4 · Objects, Classes & Data Structures ⏱ 19 min

Destructuring Objects & Arrays

By the end of this lesson you will be able to:
  • Pull values out of objects and arrays with destructuring assignment
  • Rename bindings, skip positions, and supply default values in a pattern
  • Reach into nested structures and guard against destructuring undefined

Once your data is nested inside objects and arrays, the old way to dig a value out is verbose and error-prone: const first = list[0], const name = user.name, const role = user.role. Three lines, three chances to mistype a property, and nothing on the left side that mirrors the shape of the data on the right.

Destructuring fixes that by letting a single pattern describe the shape it wants, and the engine fills in the variables to match. It works on both objects (by key) and arrays (by position), and it scales from pulling two fields out of a config to reaching deep into a nested response. What used to be a wall of assignments becomes one line that reads almost like the data itself.

flowchart LR
  P["{ name, role } pattern"] -->|"matches keys"| V["{ name: 'Ada', role: 'eng', ... }"]
  V --> B["name = 'Ada',<br/>role = 'eng'"]
  style P fill:#e0900b,color:#fff
  style V fill:#3776ab,color:#fff
A destructuring pattern mirrors the shape of the value; the named variables fill with the matching keys.

Object destructuring: pull by key

For objects, the pattern lists the keys you want, and variables of the same name receive the matching values. You can also rename on the way out with the key: newName form, and hand a default with = value for keys that might be missing:

const user = { name: "Ada", role: "engineer", country: "UK" };
const { name, role } = user;          // name='Ada', role='engineer'
const { country: from } = user;       // rename: from='UK'
const { age = 30 } = user;             // default: age=30 (key absent)

Notice the pattern reaches in by name, not position — the order you write the keys does not matter, only that they match. A default kicks in only when the key is genuinely absent (its value would be undefined); a key holding null stays null, because null is a real value, not a missing one.

Destructure by key, rename on the way out, and default a missing field. Press Run.
const user = { name: "Ada", role: "engineer", country: "UK" };

const { name, role } = user;
const { country: from } = user;
const { age = 30 } = user;

console.log(name);
console.log(role);
console.log(from);
console.log(age);

Array destructuring: pull by position

For arrays there are no keys to match — the pattern works by position, left to right. const [a, b, c] = arr puts the first element in a, the second in b, the third in c. To skip a slot you simply leave a gap with a comma: const [first, , third] = arr grabs the first and third and throws away the second.

The standout trick is the swap. Two variables trade places in a single statement with no temporary variable — [a, b] = [b, a] — because the right-hand array is built first, then unpacked into the left. What would be three lines with a temp becomes one, and it stays readable. The same positional idea lets you pull 'the rest' of an array into its own array with the rest pattern: [first, ...rest] = arr.

flowchart LR
  A["a=5, b=9"] -->|"[a,b] = [b,a]"| S["a=9, b=5"]
  style S fill:#1e7f3d,color:#fff
An array-destructuring swap builds the reversed array on the right, then unpacks it into the left.
Pull by position, skip a slot, and swap two variables with no temp.
const [first, second, third] = [10, 20, 30];
console.log(first, third);

const [a, , c] = [1, 2, 3];   // skip the middle
console.log(a, c);

let x = 5, y = 9;
[x, y] = [y, x];              // swap without a temp
console.log(x, y);

Nesting and defaults

Patterns compose, so you can destructure a value that is itself nested inside another object or array. Write const { user: { name } } = data to reach straight through data.user and pull out name — the colons trace the path inward, and only the leaf names (name) become variables; user is just a waypoint, not a binding.

Defaults stack with nesting too: every level of the pattern can offer a = fallback, so a deeply-buried field that is sometimes missing gets a sensible value instead of undefined. The rule is the same throughout — a default fills in only when the value at that spot is undefined, nothing more.

Reach through a nested object, and let defaults fill the fields that are absent.
const data = { user: { name: "Grace", age: 40 } };

const { user: { name, age, title = "staff" } } = data;
console.log(name, age, title);

const { missing = "default" } = { present: 1 };
console.log(missing);
flowchart TD
  Q{"value undefined?"} -- "yes" --> D["default applies"]
  Q -- "no (even null)" --> K["value kept as-is"]
  style D fill:#1e7f3d,color:#fff
  style K fill:#e0900b,color:#fff
A default applies only when the value is undefined; null and other real values are kept as-is.
Exercise

What does this print? The rename x: px pulls key x into a variable named px.

const point = { x: 3, y: 4 };
const { x: px, y: py } = point;
console.log(px, py);
Exercise

Complete swap(a, b) so it reverses the two values with an array-destructuring swap — write the statement [a, b] = [b, a]; first (that is the line being drilled) — then return [a, b]. So swap(1, 2) returns [2, 1].

function swap(a, b) {
  // swap a and b with one destructuring statement, then return [a, b]
}
Exercise

render() reads options.width, but calling it with no argument makes options undefined, so options.width throws TypeError. Fix it by destructuring the parameter with an empty-object default: change the signature to function render({ width = 100, height = 50 } = {}). Then render() returns 5000 instead of crashing.

function render(options) {
  return options.width * options.height;
}
Exercise

Write summary(response) that pulls the nested name and email out of response (whose shape is { user: { name, email } }) using nested object destructuring in the parameter, and returns the string "<name> <<email>>". Do not use dotted access like response.user.name. So summary({ user: { name: "Ada", email: "a@x.io" } }) returns "Ada <a@x.io>".

function summary(response) {
  // destructure name and email from response.user in the parameter, then return the string
}
Exercise

What is the value of w after const { w = 100 } = {}?

Recap

  • Object destructuring pulls values out by key: const { name, role } = user. Rename with key: newName, default with = value.
  • Array destructuring pulls by position: const [a, b] = arr. Skip a slot with a comma gap, gather the rest with ...rest.
  • The swap [a, b] = [b, a] trades two values in one statement, no temp.
  • Patterns nest: const { user: { name } } = data reaches through data.user; waypoints are not bindings.
  • Defaults apply only for undefined, and destructuring undefined itself throws — guard function parameters with = {}.

Next you'll convert these objects to and from JSON — the text format that carries them across the network and into storage.

Checkpoint quiz

After const [a, , c] = [1, 2, 3], what are a and c?

Why does function f(options) { return options.x; } throw when called as f()?

Go deeper — technical resources