Skip to Content
JavaScript Intermediate: Write Code That Doesn't Fall Apart When the Data Changes
Intermediate
0 likes
Courses
JavaScript Intermediate: Write Code That Doesn't Fall Apart When the Data Changes

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.

Course Home

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: orderId reads id and calls it orderId.
  • status = "pending" is used only when status is undefined. A null stays null.
  • customer: { city } goes into customer and takes city. Note that this does not create a variable called customer.

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 create customer.
  • Thinking a default replaces null. It only replaces undefined.
  • Treating a spread copy as a deep copy.
  • Destructuring from undefined: const { id } = undefined throws 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
Was this helpful?
​ 0 ​ 0

Comments (0)

Sign in to comment

No comments yet.