LearnFrenzy
Sign in Take a free test

Objects, Destructuring and JSON · Lesson 19 of 40

Destructuring and the Spread Operator

4 min read Updated Nov 7, 2026 Saurabh Samir

Destructuring pulls values out of objects and arrays into variables in one line. Spread (...) does the opposite: it expands an object or array into a new one. Together they appear in nearly every modern codebase – React props, API responses, Redux reducers, function options – so reading them fluently is a must.

In this lesson

  • Object destructuring: renaming, defaults, nesting
  • Rest properties to collect the remainder
  • Destructuring function parameters
  • Spread to copy, merge and update immutably

Object destructuring

const response = {
  id: 501,
  title: "JavaScript Basics",
  author: { name: "Asha", followers: 1200 },
  price: 0,
};

const { id, title } = response;
console.log(id, title);

const { title: courseName, price: cost } = response;        // rename
console.log(courseName, cost);

const { rating = 4.5, price = 999 } = response;             // defaults
console.log(rating, price);     // price exists (0), so no default

const { author: { name: authorName } } = response;          // nested
console.log(authorName);

Output

501 JavaScript Basics
JavaScript Basics 0
4.5 0
Asha

Reading the destructuring patterns

  • { id }id = obj.idsame name
  • { title: courseName }courseName = obj.titlekey: newName
  • { rating = 4.5 }obj.rating ?? 4.5**only for undefined
  • { author: { name } }name = obj.author.namenested

Common mistake: destructuring undefined

const { name } = undefined; throws a TypeError. When the object might be missing, give it a fallback: const { name } = data ?? {};. Nested destructuring throws too if a middle level is missing.

Rest properties

const user = { id: 7, name: "Ravi", password: "hash$2b...", email: "ravi@example.com" };
const { password, ...publicUser } = user;     // remove a field without mutating
console.log(publicUser);

Output

{ id: 7, name: 'Ravi', email: 'ravi@example.com' }

This is the cleanest way to strip sensitive fields before sending an object to the browser or a log.

Destructuring parameters

function createInvoice({ customer, items, taxRate = 0.18, currency = "INR" } = {}) {
  const subtotal = items.reduce((sum, { price, qty = 1 }) => sum + price * qty, 0);
  const total = subtotal * (1 + taxRate);
  return `${customer}: ${currency} ${total.toFixed(2)}`;
}
console.log(createInvoice({
  customer: "Meera",
  items: [{ price: 500, qty: 2 }, { price: 250 }],
}));

Output

Meera: INR 1475.00

The = {} default means calling createInvoice() with no argument fails with a clear error about items rather than "cannot destructure undefined". Destructured parameters also document which options a function accepts.

Spread for objects

const defaults = { theme: "light", fontSize: 16, notifications: true };
const saved = { theme: "dark" };

const settings = { ...defaults, ...saved };          // later keys win
console.log(settings);

const updated = { ...settings, fontSize: 18 };       // copy with one change
console.log(settings.fontSize, updated.fontSize);

Output

{ theme: 'dark', fontSize: 16, notifications: true }
16 18

Updating nested state immutably

const state = {
  user: { name: "Asha", prefs: { lang: "en", theme: "light" } },
  cart: [{ id: 1, qty: 1 }],
};
const next = {
  ...state,
  user: { ...state.user, prefs: { ...state.user.prefs, theme: "dark" } },
  cart: state.cart.map((item) => (item.id === 1 ? { ...item, qty: item.qty + 1 } : item)),
};
console.log(state.user.prefs.theme, next.user.prefs.theme);
console.log(state.cart[0].qty, next.cart[0].qty);
console.log(state.user === next.user, state.cart === next.cart);

Output

light dark
1 2
false false

Immutable update: copy each level you change

  1. Copy the outer object{ ...state }
  2. Copy the path down to the changeuser → prefs
  3. Set the new valuetheme: "dark"
  4. Untouched branches are sharedcheap, and change detection still works

Spread for arrays and calls

const a = [1, 2], b = [3, 4];
console.log([...a, ...b], [0, ...a, 99]);
console.log(Math.max(...b));
console.log([..."hello"].length, [...new Set("banana")].join(""));

Output

[ 1, 2, 3, 4 ] [ 0, 1, 2, 99 ]
4
5 ban
Same ..., two jobsWhereExample
Spread – expandsin literals and calls[...arr], { ...obj }, fn(...args)
Rest – collectsin parameters and destructuring(...args) =>, const { a, ...rest } = obj

Practice

Try it yourself

  1. From { data: { user: { id: 9, profile: { city: "Goa" } } } }, destructure id and city in one statement.

    Show solution
    const res = { data: { user: { id: 9, profile: { city: "Goa" } } } };
    const { data: { user: { id, profile: { city } } } } = res;
    console.log(id, city);

    Output

    9 Goa
  2. Write omit(obj, key) that returns a copy without that key.

    Show solution
    const omit = (obj, key) => {
      const { [key]: _removed, ...rest } = obj;
      return rest;
    };
    console.log(omit({ a: 1, b: 2, c: 3 }, "b"));

    Output

    { a: 1, c: 3 }

Quick check

In { ...a, ...b }, whose value wins for a shared key?

b's – later spreads overwrite earlier ones.

Does a destructuring default apply when the value is null?

No, only when it is undefined.

Is object spread a deep copy?

No – nested objects are shared; copy each level you change.

Summary

  • Destructuring extracts values with renaming, defaults and nesting.
  • Rest collects remaining properties – great for removing fields.
  • Spread copies and merges; update nested state by copying each level.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Search LearnFrenzy

Esc

Quick links