Back to CourseIntermediate4 min read
JavaScript /

Destructuring and Spread Operator

Pull values out of arrays and objects in one line, copy and merge with spread, and gather arguments with rest.

Bishal Biswas

Bishal Biswas

Self Learner

Published on: March 20, 2026
Last updated: September 5, 2026
Intermediate Topic

Array destructuring

Unpack an array into separate variables.

const colours = ['red', 'green', 'blue'];
const first = colours[0];
const second = colours[1];
const [a, b, c] = colours;
console.log(a, b, c);

Position decides which value you get.

Skipping items

const [, , third] = ['red', 'green', 'blue'];
console.log(third);

Defaults

const [x = 'default', y = 'default'] = ['only one'];
console.log(x, y);

Swapping

let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a, b);

No temporary variable needed.

Rest in arrays

const [winner, ...others] = ['Asha', 'Ben', 'Chitra', 'Dev'];
console.log(winner);
console.log(others);

The rest element must be last.

Object destructuring

Here the name decides what you get, not the position.

const user = { name: 'Asha', age: 28, city: 'Pune' };
const { name, age } = user;
console.log(name, age);

Renaming

const { name: userName, age: userAge } = user;
console.log(userName, userAge);

Useful when the incoming name would clash with something you already have.

Defaults

const { name, country = 'India' } = user;
console.log(name, country);

The default is used only when the property is undefined.

Nested

const person = {
  name: 'Asha',
  address: { city: 'Pune', pin: '411001' }
};
const { address: { city, pin } } = person;
console.log(city, pin);

Careful: this does not create an address variable. If you want both, ask for both:

const { address, address: { city } } = person;
console.log(address, city);

Rest in objects

const user = { id: 1, name: 'Asha', password: 'secret', role: 'admin' };
const { password, ...safeUser } = user;
console.log(safeUser);

A very common way to strip a field before sending data on.

Destructuring in parameters

This is where it really pays off.

function createUserOld(options) {
  const name = options.name;
  const age = options.age || 18;
  console.log(name, age);
}
function createUser({ name, age = 18, city = 'Unknown' }) {
  console.log(name, age, city);
}
createUser({ name: 'Asha', age: 28 });

The parameter list now documents exactly what the function needs.

Give the whole parameter a default so calling with no arguments still works:

function createUser({ name = 'guest', age = 18 } = {}) {
  console.log(name, age);
}
createUser();

Without = {}, calling createUser() throws.

Destructuring in loops

const users = [
  { name: 'Asha', age: 28 },
  { name: 'Ben', age: 35 }
];
for (const { name, age } of users) {
  console.log(name, age);
}
users.forEach(({ name }) => console.log(name));
const scores = { maths: 90, physics: 85 };
for (const [subject, mark] of Object.entries(scores)) {
  console.log(subject, mark);
}

The spread operator

The same ... used the other way round. Rest gathers, spread unpacks.

Copying

const original = [1, 2, 3];
const copy = [...original];
copy.push(4);
console.log(original, copy);

Merging

const a = [1, 2];
const b = [3, 4];
console.log([...a, ...b]);
console.log([0, ...a, 2.5, ...b]);

Objects

const defaults = { theme: 'light', fontSize: 14 };
const userPrefs = { theme: 'dark' };
const settings = { ...defaults, ...userPrefs };
console.log(settings);

Later values win, which makes this the standard way to apply overrides.

Updating one field without mutating:

const user = { name: 'Asha', age: 28 };
const older = { ...user, age: 29 };
console.log(user.age, older.age);

Spreading into function calls

const numbers = [5, 2, 9, 1];
console.log(Math.max(...numbers));
function add(a, b, c) {
  return a + b + c;
}
console.log(add(...[1, 2, 3]));

Strings and other iterables

console.log([...'hello']);
console.log([...new Set([1, 1, 2, 3])]);
const buttons = [...document.querySelectorAll('button')];

That last one turns a NodeList into a real array so you can use map and filter.

Shallow, not deep

const original = { name: 'Asha', address: { city: 'Pune' } };
const copy = { ...original };
copy.address.city = 'Delhi';
console.log(original.address.city);

Spread copies one level. Nested objects are still shared. Use structuredClone for a full copy.

Rest parameters

function sum(...numbers) {
  return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4));
function log(level, ...messages) {
  console.log(`[${level}]`, ...messages);
}
log('info', 'User', 'logged in');

Unlike the old arguments object, this is a real array. It must be the last parameter.

Quick recap

  • Array destructuring goes by position, object destructuring goes by name.
  • Rename with { name: newName } and set fallbacks with =.
  • Destructured parameters make a function’s inputs self documenting, and add = {} for safety.
  • ... gathers when it is a parameter and unpacks everywhere else.
  • Later values win when merging objects, which is perfect for defaults.
  • Spread is a shallow copy, so nested objects stay shared.
script.js

// Array destructuring goes by position
const colours = ['red', 'green', 'blue'];
const [first, , third] = colours;
console.log(first, third);            // red blue
// Swap without a temporary variable
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b);                    // 2 1
// Object destructuring goes by name, with renaming and defaults
const user = { name: 'Asha', age: 28, address: { city: 'Pune' } };
const { name, age: years, country = 'India' } = user;
console.log(name, years, country);    // Asha 28 India
// Nested destructuring
const { address: { city } } = user;
console.log(city);                    // Pune
// Rest strips a field out neatly
const account = { id: 1, name: 'Asha', password: 'secret' };
const { password, ...safe } = account;
console.log(safe);                    // { id: 1, name: 'Asha' }
// Destructured parameters document what a function needs
function createUser({ name = 'guest', age = 18 } = {}) {
  return name + ' (' + age + ')';
}
console.log(createUser({ name: 'Ben', age: 35 }));
console.log(createUser());            // = {} makes this safe
// Spread copies and merges. Later values win
const defaults = { theme: 'light', fontSize: 14 };
const prefs = { theme: 'dark' };
console.log({ ...defaults, ...prefs });   // theme: dark, fontSize: 14
// Update one field without mutating the original
const older = { ...user, age: 29 };
console.log(user.age, older.age);         // 28 29
// Spread into function calls and over any iterable
console.log(Math.max(...[5, 2, 9, 1]));   // 9
console.log([...'hello']);                // ['h','e','l','l','o']
console.log([...new Set([1, 1, 2, 3])]);  // [1, 2, 3]
// Rest parameters gather arguments into a real array
function sum(...numbers) {
  return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4));             // 10
// Remember: spread is shallow, nested objects are still shared
const copy = { ...user };
copy.address.city = 'Delhi';
console.log(user.address.city);           // Delhi, changed too

Was this explanation helpful?

Your feedback helps us improve the content for everyone.