Destructuring and Spread Without the Confusion
Pull values out of objects and arrays, use options objects with defaults, and copy data with spread, including the shallow-copy trap. After this, modern JavaScript stops looking like punctuation.
Course content
Sign in to save your progress.
-
Set Up Projects That Don't Break Each Other
-
Node.js and npm: Running JavaScript Outside the Browser
-
package.json and Lock Files: Same Install Every Time
-
-
-
Organize Code Other People Can Read
-
JavaScript Modules: import, export and Splitting One Big File
-
JavaScript Classes: When an Object Literal Is No Longer Enough
-
Destructuring and Spread Without the Confusion
-
-
-
Work With Real Data
-
Array Methods: map, filter, reduce and When a Loop Is Clearer
-
Working With JSON: parse, stringify and What Breaks
-
Dates in JavaScript Without Losing a Day
-
-
-
Waiting for Things
-
Callbacks to Promises: Why the Code Runs Out of Order
-
async/await and fetch: Calling a Real API
-
-
-
Prove It Works Before Someone Else Finds Out
-
try/catch and Custom Errors
-
Your First Tests With Vitest
-
-
- Final Check: JavaScript Intermediate
Skip ahead: If you already read const { id, ...rest } = order without stopping, skip to "Spread copies are shallow".
Destructuring and spread are short syntax for two things you do all day: pulling values out of an object or array, and copying values into a new one. They're everywhere in modern code, so you need to read them even if you'd rather not write them. The good news: there are only a few patterns.
Destructuring objects: pull values out
Instead of three lines:
const id = order.id;
const status = order.status;
const customer = order.customer;
one line:
const order = { id: 1042, status: "paid", customer: { name: "Ana", city: "Cebu" } };
const { id, status } = order;
console.log(id, status);
1042 paid
The names inside the braces must match the property names. You can rename, set a default, and go one level down:
const order = { id: 1042, customer: { name: "Ana", city: "Cebu" } };
const { id: orderId, status = "pending", customer: { city } } = order;
console.log(orderId, status, city);
1042 pending Cebu
id: orderIdreadsidand calls itorderId.status = "pending"is used only whenstatusisundefined. Anullstaysnull.customer: { city }goes intocustomerand takescity. Note that this does not create a variable calledcustomer.
Destructuring arrays: by position
Arrays are unpacked by position, not name:
const [first, second, ...others] = ["rice", "eggs", "coffee", "bread"];
console.log(first, second, others);
const [, , third] = ["rice", "eggs", "coffee"];
console.log(third);
rice eggs [ 'coffee', 'bread' ]
coffee
You'll see this most with functions that return a pair, like const [error, data] = await safeFetch(url).
In function parameters
This is where destructuring earns its keep. Compare:
createInvoice(1042, true, false, "PHP");
with:
function createInvoice({ orderId, sendEmail = false, includeVat = true, currency = "PHP" }) {
return `Invoice for ${orderId}: email=${sendEmail}, vat=${includeVat}, ${currency}`;
}
console.log(createInvoice({ orderId: 1042, sendEmail: true }));
Invoice for 1042: email=true, vat=true, PHP
The call says what each value means, the order doesn't matter, and the defaults are in the signature. The tutorial Destructuring Function Parameters With Defaults covers the one trap here: calling it with no argument at all.
Spread: copy values into a new object or array
The same three dots, on the other side of the =, copy instead of collect:
const order = { id: 1042, status: "pending", items: ["rice"] };
const paid = { ...order, status: "paid" };
console.log(order.status, paid.status);
const more = [...order.items, "eggs"];
console.log(order.items, more);
pending paid
[ 'rice' ] [ 'rice', 'eggs' ]
Later properties win, so { ...order, status: "paid" } copies everything and then overwrites status. The original isn't touched. This is how you "change" data without changing it, which most front-end frameworks expect.
Rest: collect what's left
On the left side, ... collects the remainder:
const order = { id: 1042, status: "paid", internalNote: "VIP, check stock" };
const { internalNote, ...publicOrder } = order;
console.log(publicOrder);
{ id: 1042, status: 'paid' }
Handy for removing a field before you send data somewhere it shouldn't go.
Spread copies are shallow
This is the mistake that costs people hours.
const order = { id: 1042, customer: { name: "Ana" } };
const copy = { ...order };
copy.customer.name = "Ben";
console.log(order.customer.name);
Ben
Spread copies the top level only. copy.customer and order.customer are the same object, so changing one changes the other. For a full copy of plain data, use structuredClone(order).
⚠ Warning: Don't use spread to "back up" data before editing nested parts. The backup shares the nested objects and changes with them. If you need an untouched original, structuredClone it, or copy each level you change: { ...order, customer: { ...order.customer, name: "Ben" } }.
💡 Tip: Read ... by its side of the =. On the left (or in parameters), it collects into a new variable. On the right (or in a call), it spreads values out.
Common mistakes
- Expecting
customer: { city }to also createcustomer. - Thinking a default replaces
null. It only replacesundefined. - Treating a spread copy as a deep copy.
- Destructuring from
undefined:const { id } = undefinedthrows a TypeError. - Destructuring ten levels deep in one line. Two lines are easier to debug.
What happens if you skip this
You won't be able to read most JavaScript written in the last few years, and when you start writing it, the shallow copy will get you. A screen edits a "copy" of an order, the original changes too, and the cancel button no longer cancels anything.
In const { a, ...b } = obj, what's in b if obj only has a?
About
Pull values out of objects and arrays, use options objects with defaults, and copy data with spread, including the shallow-copy trap. After this, modern JavaScript stops looking like punctuation.
- Duration
- 5 min
- Last update
- 08/28/2026
Comments (0)
Sign in to commentNo comments yet.