Structured data: arrays and objects · Lesson 26 of 77 · practice

Unpacking and rebuilding: destructuring and spread

About 60 minutes.

By the end of this lesson you can

  • Pull named properties out of an object into their own variables in one statement.
  • Pull elements out of an array by position, skipping the ones you do not want.
  • Give a destructured value a default, and say exactly when that default is used.
  • Copy and merge arrays and objects with spread, and predict which value wins on a clash.
  • Use rest to collect everything else from an object or an array.

The three lines you keep writing

Why it matters

Functions that take an object almost always begin the same way: pull out the properties you need, give a couple of them a fallback, then get on with the work. Written by hand that is three or four lines of `const name = user.name;` before anything interesting happens.

Destructuring collapses that into one statement, and it does something better than saving keystrokes: it puts the shape you are expecting at the top of the function where a reader can see it. Someone skimming your code learns what a `user` needs to contain without reading the body.

Spread is the mirror image. Destructuring takes a shape apart; spread builds a new one out of the pieces. Between them they are how modern JavaScript moves data around without mutating it, which is the habit Lesson 2 started and Lesson 6 explains.

Taking an object apart

Explanation

`const { name, age } = user;` creates two variables, `name` and `age`, holding `user.name` and `user.age`. The braces on the left are not an object — they are a pattern describing which properties you want out.

The names in the pattern have to match the keys exactly, because they are the keys. `const { nmae } = user` gives you a variable called `nmae` holding `undefined`, with no complaint, which is the same silent-typo problem as Lesson 3 wearing a different hat.

Nothing new happens at runtime. `const { name } = user` does precisely what `const name = user.name` does, and the object is not modified or consumed. The gain is entirely in what the code communicates.

Renaming, and defaults that fire less often than you think

Explanation

You can rename as you unpack: `const { city: town } = user;` reads `user.city` into a variable called `town`. Read the colon as goes into. This is needed more often than it sounds — when two objects both have a `name`, or when the key is not a legal variable name.

You can also supply a default: `const { city = 'Unknown' } = user;`. If `user` has no `city`, `city` is `'Unknown'`.

Now the detail that catches everyone. MDN states it exactly: the default value is used when the property is not present, or has the value `undefined`; it is not used if the property has the value `null`. So `const { city = 'Unknown' } = { city: null }` gives you `null`, not `'Unknown'`. If you want `null` treated as missing too, that is a different tool — `??`, in Lesson 7.

Taking an array apart

Explanation

Arrays destructure by position instead of by name, using square brackets: `const [first, second] = pair;`. The names are yours to choose, because it is the position that identifies the element.

An empty slot skips a position: `const [first, , third] = list;` takes index 0 and index 2 and ignores index 1. The double comma looks like a typo and is not.

Defaults work the same way and fire under the same condition: `const [a, b = 'z'] = ['x'];` gives `b` the value `'z'`, because there is no index 1 and a missing element is `undefined`.

Spread: building a new one out of an old one

Explanation

Inside an array literal, `...list` means put every element of `list` here. `[0, ...nums, 3]` builds a brand-new array with the elements of `nums` in the middle. `[...list]` on its own is a copy — the same thing `slice()` gave you in Lesson 2, written shorter.

Inside an object literal, `{ ...user }` means put every property of `user` here. Spreading two objects merges them: `{ ...defaults, ...chosen }`.

When both objects have the same key, MDN's rule is that the property takes the last value assigned while remaining in the position it was originally set. So the later spread wins the value, and the key keeps the place it first appeared. Reading these literals aloud as and then makes the order obvious, and getting the two the wrong way round is one of the easiest bugs to stare straight past.

Rest: and everything else

Explanation

The same `...` in a destructuring pattern means the opposite: collect whatever is left. `const { id, ...details } = record;` gives you `id`, plus a new object `details` holding every other property. `const [first, ...others] = list;` gives you the first element and a new array of the rest.

Rest must come last in the pattern — there is no sensible meaning for everything else followed by something else. Writing it anywhere but the end is a syntax error.

This is the neat way to remove a property without `delete`, and the standard way to say I handle these two options and pass the remainder on.

What spread does not do

A common mistake

MDN is explicit that spread effectively goes one level deep while copying: it creates a shallow copy. `{ ...settings }` gives you a new outer object, but any object or array nested inside it is the very same one the original holds. Changing `copy.window.width` changes the original's too.

This is not a flaw to work around so much as a fact to know. Lesson 6 is entirely about what it means and what to do when you need more.

One smaller trap: spreading an array into an object literal, `{ ...list }`, is legal and gives you an object with `'0'`, `'1'`, `'2'` as keys. It is almost never what anyone wanted. Match the brackets to the shape you are building.

Recap

Recap

Destructuring unpacks by name from objects and by position from arrays, and the pattern goes on the left of the `=`. Defaults fire for a missing or `undefined` value and never for `null`. Spread rebuilds: later values win a clash, keys keep the position they first appeared in, and the copy is one level deep. Rest collects the remainder and must come last.

Worked examples

Every output below was produced by actually running the code in the same sandbox your exercises run in — it is not a prediction.

Unpacking, renaming and defaulting

Three destructuring statements against the same object: a plain unpack, a rename with a default for a key that is absent, and a default for a key that is missing entirely.

1const user = { name: "Ada", city: "London", age: 36 };2 3const { name, age } = user;4console.log(name);5console.log(age);6 7const { city: town, country = "UK" } = user;8console.log(town);9console.log(country);10 11const { nickname = "none" } = user;12console.log(nickname);
  • line 3

    The braces are a pattern, not an object. Two variables come out.

  • line 7

    `city: town` reads user.city into a variable called town.

  • line 7

    There is no country property, so the default is used.

  • line 11

    Same again: a missing property takes the default rather than being undefined.

What it prints

Ada36LondonUKnone

The default that does not fire

Two nearly identical objects, one holding undefined and one holding null, unpacked with the same default. Only one of them gets it.

1const a = { size: undefined };2const b = { size: null };3 4const { size: sizeA = "medium" } = a;5const { size: sizeB = "medium" } = b;6 7console.log(sizeA);8console.log(sizeB);
  • line 4

    The property exists but holds undefined, which counts as absent. The default fires.

  • line 5

    null is a real value, so the default does NOT fire — this is the rule people forget.

  • line 8

    Prints null, not 'medium'. Lesson 7's ?? is the tool for treating null as missing.

What it prints

mediumnull

Merging two objects, in both orders

The same two objects spread into one literal twice, with the order swapped. The results differ in both value and key order, which is the whole rule made visible.

1const defaults = { theme: "light", fontSize: 14 };2const chosen = { fontSize: 18 };3 4console.log({ ...defaults, ...chosen });5console.log({ ...chosen, ...defaults });6 7const nums = [1, 2];8console.log([0, ...nums, 3]);9console.log([...nums]);
  • line 4

    Defaults first, then chosen overrides: fontSize is 18. This is the order you almost always want.

  • line 5

    Reversed, so defaults wins: fontSize is 14. Note that fontSize also keeps FIRST position, because it appeared first.

  • line 8

    Array spread splices the elements in; the result is a new array.

  • line 9

    Spread on its own is a copy — the same job as slice().

What it prints

{"theme":"light","fontSize":18}{"fontSize":14,"theme":"light"}[0,1,2,3][1,2]

Rest, on an object and on an array

Pull one thing out and keep the remainder, which is how you remove a property or handle the first element specially.

1const record = { id: 7, name: "Ada", city: "London" };2const { id, ...details } = record;3 4console.log(id);5console.log(details);6 7const [first, ...others] = ["a", "b", "c"];8console.log(first);9console.log(others);
  • line 2

    id comes out; details is a NEW object holding everything else.

  • line 5

    The original record is untouched — this is a copy of the remainder.

  • line 7

    The same idea by position: one element out, a new array of the rest.

What it prints

7{"name":"Ada","city":"London"}a["b","c"]

Check yourself

Not scored, not stored. Getting one wrong is the useful part.

What does this print?

const settings = { volume: null };const { volume = 5 } = settings;console.log(volume);

Given `const a = { x: 1 }; const b = { x: 2 };` — what is `{ ...a, ...b }`?

What does this print?

const [first, , third = "z"] = ["a", "b"];console.log(first);console.log(third);

Where must a rest element appear in a destructuring pattern?

Write it yourself

Exercise · core · 7 tests

Unpack a user, and swap a pair

Write `summarise(user)`, which destructures `name`, `city` and `role` out of the user and returns the string `name + ' - ' + role + ' in ' + city`. Missing values must fall back: `city` to 'Unknown' and `role` to 'member'. Then write `swap(pair)`, which takes a two-element array and returns a NEW two-element array with the elements the other way round.

What your code must do

The fallbacks are destructuring defaults, so they apply to a missing property or one holding undefined, and NOT to one holding null — a city of null must appear in the sentence as null. swap must not modify the array it was given.

Define summarise and swap at the top level of your code — the tests call them by name.

Runs on your device in a sandbox with no network and no access to this page.

Exercise · core · 9 tests

Rebuild instead of modify

Write three functions that all return something new and change nothing. `withDefaults(defaults, chosen)` returns one object where values in `chosen` win over the ones in `defaults`. `appended(list, value)` returns a new array with `value` on the end. `withoutKey(record, key)` returns a new object with that key removed.

What your code must do

The tests compare objects by their JSON text, so key order matters: withDefaults must produce the keys of `defaults` in their original order first, followed by any keys only `chosen` has. No argument may be modified, and removing a key that is not there returns an unchanged copy.

Define withDefaults, appended and withoutKey at the top level of your code — the tests call them by name.

Runs on your device in a sandbox with no network and no access to this page.

Worth remembering

  • When does a destructuring default fire?

    Only when the property is absent or holds undefined. Never for null — null is a real value and is kept. Use ?? if you want null treated as missing.

  • In { ...a, ...b }, which value wins a shared key, and where does the key sit?

    b's value wins, because the property takes the last value assigned. The key keeps the position where it first appeared, which is a's position.

  • What does ... mean on the left of an = versus inside a literal?

    On the left (a pattern) it is rest: collect everything else, and it must come last. Inside a literal it is spread: put all of these here.

Check this lesson against the source

We wrote the explanation; we did not invent the facts. This is the page that backs them.

MDN — Destructuring

Check there: The sentence that a default value is used when the property is not present, or has value undefined, and that it is not used if the property has value null; plus the rules for renaming and for rest properties coming last.

MDN — Spread syntax (...)

Check there: That a duplicated property takes the last value assigned while keeping the position it was originally set, and that spread goes one level deep — a shallow copy.

Prefer to read the source in a structured breakdown? Turn this doc into a breakdown

0 of 2 exercises in this lesson solved on this device

Consent version 2026-07-31.1

Cookie preferences