Destructuring and spread
Two pieces of syntax have been appearing without explanation since module 2 —
the [index, value] in .entries(), and the ... that copies an object. Both
are here, and both are everywhere in modern JavaScript.
Pulling values out of an object
The long way:
const order = { customer: 'Priya', plates: 3, paid: true };
const customer = order.customer;
const plates = order.plates;
The short way:
const { customer, plates } = order;
console.log(customer, plates);
Priya 3
The braces on the left are not an object — they are a pattern. "Find a
customer property and a plates property, and make variables with those
names."
Order does not matter, and you take only what you need.
Renaming
When the property name is wrong for your code, or would collide:
const { customer: name } = order;
console.log(name);
Priya
Read it as "customer, as name". The colon is not a type annotation — a reasonable guess, and wrong.
Defaults
const { rate = 80 } = order;
console.log(rate);
80
order has no rate, so the default applies. And the same trap as function
parameters in module 3:
const { discount = 10 } = { discount: null };
console.log(discount);
null
A default fires for undefined only, never for null. Since APIs and
databases send null for "no value" constantly, this catches people repeatedly.
The next lesson's ?? is the tool for that case.
Nested
const order = { customer: 'Priya', address: { city: 'Pune', pin: '411014' } };
const { address: { city } } = order;
console.log(city);
Pune
Note what that did not create: there is no address variable, only city.
The address: part is navigation, not a declaration.
Nesting also inherits the deep-read problem:
const { address: { city } } = { customer: 'Arjun' };
TypeError: Cannot read properties of undefined (reading 'city')
Two levels of destructuring, two levels that must exist. Destructuring null
fails too, with a message that names the property:
TypeError: Cannot destructure property 'a' of 'null' as it is null.
Rest
... collects everything you did not name:
const { customer, ...rest } = { customer: 'Priya', plates: 3, paid: true };
console.log(rest);
{ plates: 3, paid: true }
This is the clean way to remove a field: take it out, keep the rest.
Arrays destructure by position
const [first, second] = ['dal', 'rice', 'atta'];
console.log(first, second);
dal rice
Position, not name. Skip with a gap, and defaults work here too:
const [first, , third = 'ghee'] = ['dal', 'rice'];
console.log(first, third);
dal ghee
The empty slot skips 'rice', and third falls back because there is no third
item.
This is what .entries() was doing all along:
for (const [index, item] of ['dal', 'rice'].entries()) {
console.log(index, item);
}
0 dal
1 rice
And the neatest trick in the language — swapping without a temporary:
let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a, b);
2 1
Destructuring parameters
This is where it earns its keep. Instead of:
function describe(order) {
return `${order.customer} ordered ${order.plates}`;
}
write:
function describe({ customer, plates = 1 }) {
return `${customer} ordered ${plates}`;
}
console.log(describe({ customer: 'Arjun' }));
Arjun ordered 1
The signature now documents what the function actually uses. You can read what it needs without reading the body — which is exactly what you want when the object has fifteen fields and the function touches two.
One caution: describe() with no argument at all throws, because it cannot
destructure undefined. Give the parameter a default of {} if it is optional.
Spread: the same dots, the other direction
Destructuring takes things apart; spread puts them together.
const items = ['dal', 'rice'];
const more = [...items, 'atta'];
console.log(more);
console.log(items);
[ 'dal', 'rice', 'atta' ]
[ 'dal', 'rice' ]
The original is untouched — this is the non-mutating way to add to a list, and what the last lesson's practice was asking for.
Objects work the same, and later keys win:
console.log({ rate: 80, ...{ rate: 95 } });
console.log({ ...{ rate: 95 }, rate: 80 });
{ rate: 95 }
{ rate: 80 }
That ordering is the whole technique for applying updates:
const order = { customer: 'Priya', plates: 3, paid: false };
const updated = { ...order, paid: true };
console.log(updated);
console.log(order.paid);
{ customer: 'Priya', plates: 3, paid: true }
false
A new object with one field changed, original intact. This is how React, Redux and every state library expect you to update data, and the habit is worth building now.
Spread also turns an array into arguments:
console.log(Math.max(...[3, 9, 2]));
9
Math.max takes separate numbers, not an array, and the dots do the unpacking.
The shallow-copy trap, again
Spread copies one level. From the objects lesson, but it matters enough to repeat:
const base = { customer: 'Priya', address: { city: 'Pune' } };
const clone = { ...base };
clone.address.city = 'Mumbai';
console.log(base.address.city);
Mumbai
So { ...order, paid: true } is safe — paid is a plain value. But
{ ...order } followed by clone.address.city = ... is not. To update something
nested, spread at each level:
const updated = {
...order,
address: { ...order.address, city: 'Mumbai' },
};
Verbose, and honest about what it is doing. For anything deeper than two levels,
structuredClone or a library is kinder.
Check your work
const { customer, plates } = order creates two variables from the matching
properties. Order in the pattern does not matter.
const { customer: name } creates name, not customer. Read it as
"customer, as name".
const { rate = 80 } = order gives 80 when order has no rate — but
const { discount = 10 } = { discount: null } gives null. Defaults fire
for undefined only.
const { address: { city } } = order creates city and not address.
If address is missing it throws Cannot read properties of undefined (reading 'city').
const { customer, ...rest } = order puts everything else in rest. The
cleanest way to drop a field.
const [first, , third = 'ghee'] = ['dal', 'rice'] gives dal and ghee —
the gap skips a position, and the default fills a missing one.
[a, b] = [b, a] swaps them.
function describe({ customer, plates = 1 }) called with { customer: 'Arjun' } gives Arjun ordered 1. Called with no argument at all, it throws —
use = {} on the parameter if it is optional.
{ rate: 80, ...{ rate: 95 } } is { rate: 95 }; reversed it is { rate: 80 }. Later keys win, which is what makes { ...order, paid: true } an update.
{ ...order, paid: true } leaves order unchanged.
Spread is shallow. { ...base } then changing clone.address.city also
changes base. Spread each level, or structuredClone.
Math.max(...[3, 9, 2]) is 9.
moveCity needs a spread at every level it changes:
function moveCity(order, city) {
return { ...order, address: { ...order.address, city } };
}
{ ...order, address: { ...order.address, city } } builds a new order and a
new address. The single-spread version, { ...order } followed by
copy.address.city = city, shares the original address object — so changing
the copy's pincode afterwards would also change the original's. Test it by
mutating the result and then reading the original: with the version above, the
original still reads Pune and 411014.
Practice
- Destructure
customerandplatesout of an order in one line. - Rename
customertonamewhile destructuring. - Give
ratea default and confirm it applies. Then set the property tonulland watch the default stand aside. - Destructure a nested
city. Then confirm noaddressvariable exists. - Destructure a nested property from an object missing the middle level, and read the error.
- Use rest to pull
paidout and keep everything else. - Destructure the first and third items of an array, skipping the second.
- Swap two variables with array destructuring.
- Rewrite a function to destructure its parameter with a default. Then call it with no argument and read the error.
- Use spread to add an item to a list and to update one field of an object, proving in both cases that the original is unchanged.
- Harder. Given
{ customer: 'Priya', address: { city: 'Pune', pin: '411014' } }, writemoveCity(order, city)returning a new order with a new city, where neither the original order nor its original address is modified. Prove it by changing the result afterwards and checking the original. A single spread will look right and fail this test — that is the exercise.
Next: optional chaining — the fix for the Cannot read properties of undefined
error this lesson has now caused twice.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship