Command Palette

Search for a command to run...

Hectal
PHASE 6Beginner ~26 min· topic 3 of 7

Topic 6.3

Destructuring and Spread with Objects

In one line

Destructuring pulls properties out of an object into variables in one line, with renaming, defaults and a rest object for whatever is left; spread ... copies one object's properties into a new one, which is how you merge objects.

Think of it like this

Unpacking a school bag. Instead of saying "take the bag, take out the lunchbox, put it here; take the bag again, take out the water bottle, put it there", you say once: "lunchbox here, bottle there, everything else in the cupboard". Destructuring is that one sentence. Spread is the reverse: tipping several bags into one new bag, where a later bag's lunchbox replaces an earlier one's.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Destructuring
Pulling values out of an object (or array) into separate variables in one statement.
Pattern
The { ... } on the left of = that describes which properties to pull out.
Default value
A fallback in a pattern, { role = "guest" }, used when the property is undefined.
Rest property
...rest in a pattern: a new object holding all the properties you didn't name.
Spread
...obj inside an object literal: copy all of obj's properties into the new object.
Shallow copy
A new object whose top-level properties are copied, while nested objects are still shared.
Enumerable
A normal, visible property that shows up when you list or copy an object's keys.
Object.assign
An older method that copies properties from source objects into a target object.

Step by step

01Pull out two properties

List the keys you want inside braces on the left. Each becomes a variable with the same name.

app.jswhole filejavascript
const user = { name: "Meera", city: "Delhi", age: 15 };
const { name, city } = user;
console.log(name, city); // Meera Delhi

02Rename and add defaults

key: newName renames. = value gives a default for missing or undefined values. You can do both: the rename comes first, then the default.

app.jswhole filejavascript
const { city: hometown, role = "student", name: fullName = "Anonymous" } = user;
console.log(hometown, role, fullName); // Delhi student Meera

03Destructure a parameter

Functions that take many options usually take one object. Destructuring in the parameter list documents which options exist and gives each one a default. The = {} at the end lets callers pass nothing at all.

app.jswhole filejavascript
function greet({ name = "friend", excited = false } = {}) {
  return "Hello, " + name + (excited ? "!" : ".");
}
console.log(greet({ name: "Asha", excited: true })); // Hello, Asha!
console.log(greet()); // Hello, friend.

04Leave some keys out with rest

Name the keys you want to drop; ...rest gets everything else as a brand new object. The original is untouched.

app.jswhole filejavascript
const account = { id: 7, email: "a@example.com", password: "secret" };
const { password, ...safe } = account;
console.log(safe); // { id: 7, email: 'a@example.com' }

05Merge with spread: later wins

Spread copies properties left to right into a new object. When two sources have the same key, the one further right overwrites the earlier one, but the key keeps its first position.

Merge with spread: later winsdiagram
Rendering diagram…

Try it yourself

  1. 1

    Defaults and null

    In the first example, change role: null to role: undefined. Predict the first line before you run it.

  2. 2

    Swap the spread order

    In the third example, change the merge to { ...saved, ...defaults }. Predict the settings line: which theme wins now?

  3. 3

    Fix the shallow copy

    In the last example, change the copy to { ...original, members: [...original.members] }. Predict all three lines, then run.

Code & diagrams

Rename and defaults ES2015+ New tab

`age` was missing, so its default was used. `role` was `null`, which is not `undefined`, so the default `"guest"` was **not** used. Defaults ignore `0` and `""` too.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Meera Delhi 18 null
0 ""
Destructuring in parameters ES2015+ New tab

`author: { name }` reaches inside `author`. Its default `{ name: "unknown" }` is used when `author` is missing, and the final `= {}` handles a call with no argument at all.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Malgudi Days by R. K. Narayan, 256 pages
My Notes by unknown, 0 pages
Untitled by unknown, 0 pages
Rest to remove, spread to merge ES2018+ New tab

Rest and spread make new objects; `account` and `defaults` are unchanged. `Object.assign` instead writes into its first argument and returns it, skipping `null` sources.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

{ id: 7, email: 'asha@example.com', plan: 'free' }
6 4
{ theme: 'dark', fontSize: 18, sound: false }
{ theme: 'light', fontSize: 16, sound: true }
{ a: 9, b: 2 } true
Spread is shallow ES2018+ New tab

Changing `copy.name` didn't touch the original, but both objects hold the **same** `members` array. Topic 6.5 shows how to copy nested data too.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Team A [ 'Asha', 'Ravi', 'Meera' ]
Team B [ 'Asha', 'Ravi', 'Meera' ]
true

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Destructure undefined

Remove = {} from the end of the parameter in the second example and run it.

terminal
$ Console
── what you'll see ──
Uncaught TypeError: Cannot read properties of undefined (reading 'title')

Break #2

Assign to existing variables without parentheses

Write let a, b; then { a, b } = { a: 1, b: 2 };.

terminal
$ Console
── what you'll see ──
Uncaught SyntaxError: Unexpected token '='

Myth vs fact

Myth

A default in destructuring replaces any empty value.

Fact

It replaces only undefined. null, 0, false and "" are kept. Use ?? after destructuring if null should also fall back.

Myth

{ ...obj } makes a full independent copy.

Fact

It is shallow: nested objects and arrays are shared. Use structuredClone (Topic 6.5) for a deep copy.

Myth

Spread and Object.assign are the same thing.

Fact

Spread always creates a new object and defines properties on it. Object.assign writes into an existing target, so it triggers that target's setters (Topic 6.7) and changes it.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Spread and rest copy only own enumerable properties, including symbol keys. Getters on the source are called and their values copied, so the copy has plain data properties, not getters. Methods inherited from a prototype (including class methods) are not copied, which is why spreading a class instance gives a plain object.

  • ▸

    Defaults are evaluated lazily, only when needed, and can refer to earlier variables in the same pattern: const { w = 10, h = w * 2 } = {}; gives h = 20.

  • ▸

    Object rest must be the last element and can't have a trailing comma. In parameter lists, a rest property is a common way to forward options: function Button({ label, ...props }) { ... }, used heavily in React components.

  • ▸

    Merging with spread in a loop (acc = { ...acc, [k]: v }) copies the whole object every time, which is O(n²) for n keys. In hot loops, mutate a local object you created, or build with Object.fromEntries (Topic 6.4).

Remember this

  1. 1

    Object destructuring (ES2015) creates variables from properties by name: const { name, age } = user; is the same as const name = user.name; const age = user.age;. The variable names must match the keys; the order doesn't matter. A key that doesn't exist gives undefined. Destructuring null or undefined throws a TypeError, because there's nothing to read from.

  2. 2

    Renaming uses a colon: const { name: fullName } = user; reads user.name into a variable called fullName. Read the colon as "goes into". A default value uses =: const { role = "guest" } = user;. The default is used **only when the value is undefined**, not when it is null, 0 or "". You can combine both: const { name: fullName = "Anonymous" } = user;.

  3. 3

    Nested destructuring follows the shape of the data: const { address: { city } } = user; creates only city (not address). If address is missing it throws, so give it a default: const { address: { city } = {} } = user;. Destructuring also works in function parameters: function show({ title, pages = 0 }) {...} reads the properties from the object passed in. Add = {} after the pattern so calling the function with no argument doesn't throw.

  4. 4

    A rest property (ES2018), const { password, ...rest } = account;, collects all the remaining own enumerable properties (the normal, visible ones) into a new object. It's the clean way to make a copy without some keys, without using delete on the original.

  5. 5

    Object spread (ES2018), { ...a }, copies a's own enumerable properties into a new object. With several sources, { ...defaults, ...saved, sound: false }, properties are copied left to right, so later ones win. That makes spread the standard way to merge settings and to update one field without changing the original: { ...user, age: 15 }. Spreading null or undefined is allowed and adds nothing. The older equivalent is Object.assign(target, ...sources), which changes target in place.

  6. 6

    Spread makes a shallow copy: the top-level properties are copied, but nested objects and arrays are shared, not duplicated. Change copy.members.push(...) and the original's members changes too. Topic 6.5 explains why (references) and how to make deep copies. One more syntax rule: to destructure into variables that already exist, wrap the statement in parentheses, ({ a, b } = obj);, or the { is read as a block.

Explain it without notes

01

Explain object destructuring with renaming and defaults, and when a default is used.

02

What's the difference between object rest and object spread?

03

Why is { ...obj } called a shallow copy, and what bug does that cause?

04

How does spread differ from Object.assign?

Practice

01

Given const movie = { title: "Lagaan", year: 2001, rating: null }, destructure title into name, year, and rating with a default of "not rated". Log all three and explain the rating.

02

Write withoutKeys(obj) that returns a copy of obj without createdAt and updatedAt, leaving the original unchanged.

03

Write updateEmail(user, email) that returns a new user with the new email, without changing user. Prove it by logging both.

Trade-offs

  • ↔

    Destructuring in parameters makes options self-documenting, but deep patterns with renames and defaults become hard to read; past two levels, plain property access is often clearer.

  • ↔

    Spread makes new objects, which keeps updates safe and easy to track, at the cost of copying; for large objects updated in tight loops, mutating a private local object is faster.

Done when you can

  • I can destructure properties, rename them and give defaults.

  • I know a default applies only to undefined.

  • I can destructure nested objects and function parameters safely with = {}.

  • I can remove keys with a rest property and merge objects with spread.

  • I can explain why spread is a shallow copy.