JavaScriptIntermediate

JavaScript Destructuring, Spread and Rest Explained Simply

Learn array and object destructuring, the spread syntax and rest parameters in JavaScript, with simple real-life examples for copying, merging and unpacking.

All JavaScript lessons

What you will learn

Three small pieces of modern JavaScript make your code much shorter and cleaner: destructuring, spread and rest. They look a bit similar, because they use [ ], { } and ..., but each has a different job:

  • Destructuring unpacks values out of arrays and objects into variables.
  • Spread expands an array or object into separate items.
  • Rest collects many items into one array or object.

In this lesson you will learn all three, step by step, with real examples.

Array destructuring

Without destructuring, getting items out of an array looks like this:

const colors = ["red", "green", "blue"];

const first = colors[0];
const second = colors[1];

console.log(first, second);   // "red" "green"

With array destructuring, you do it in one line. Put variable names inside [ ] on the left side:

const colors = ["red", "green", "blue"];

const [first, second] = colors;

console.log(first);    // "red"
console.log(second);   // "green"

The variables are matched by position: the first name gets the first item, the second name gets the second item, and so on. The original array is not changed.

Skip items

Leave a gap with a comma to skip an item:

const [, , third] = ["red", "green", "blue"];

console.log(third);   // "blue"

const [firstColor, , lastColor] = ["red", "green", "blue"];
console.log(firstColor, lastColor);   // "red" "blue"

Default values

If there is no item at that position, the variable is undefined. Give it a default value with =:

const [a, b, c = "unknown"] = ["x", "y"];

console.log(a, b, c);   // "x" "y" "unknown"

The default is used only when the value is undefined, not for null or 0.

Swap two variables

let x = 1;
let y = 2;

[x, y] = [y, x];

console.log(x, y);   // 2 1

No temporary variable needed. (Put a ; at the end of the line before, or JavaScript may join the two lines by mistake.)

Collect the remaining items with rest

const [winner, ...others] = ["Riya", "Karan", "Neha", "Aman"];

console.log(winner);   // "Riya"
console.log(others);   // ["Karan", "Neha", "Aman"]

...others collects everything that is left into a new array. It must be the last item.

Nested arrays

const [id, [lat, lng]] = [7, [28.61, 77.2]];

console.log(id, lat, lng);   // 7 28.61 77.2

Works on any iterable, like strings

const [firstLetter, secondLetter] = "Hello";

console.log(firstLetter, secondLetter);   // "H" "e"

Object destructuring

Objects are unpacked by name, not by position. Use { } on the left side and write the property names:

const user = { name: "Riya", age: 20, city: "Delhi" };

const { name, age } = user;

console.log(name);   // "Riya"
console.log(age);    // 20

The order does not matter, because JavaScript looks for the key names:

const { city, name: userName } = user;   // "city" first, then "name"

Rename the variable

If you want a different variable name, write key: newName:

const user = { name: "Riya", age: 20 };

const { name: fullName, age: years } = user;

console.log(fullName);   // "Riya"
console.log(years);      // 20

Read name: fullName as “take the property name and store it in fullName”. Renaming is useful to avoid clashes, or to make a variable clearer.

Default values

If a property is missing (undefined), use a default:

const user = { name: "Riya" };

const { name, role = "student", age = 18 } = user;

console.log(name, role, age);   // "Riya" "student" 18

You can combine a rename and a default:

const { city: town = "Unknown" } = { name: "Riya" };

console.log(town);   // "Unknown"

Nested objects

const order = {
  id: 101,
  customer: {
    name: "Karan",
    address: { city: "Mumbai", pin: "400001" }
  }
};

const {
  id,
  customer: {
    name,
    address: { city }
  }
} = order;

console.log(id, name, city);   // 101 "Karan" "Mumbai"

Only id, name and city become variables. customer and address here are just paths to reach them. Do not go too deep, because very long patterns are hard to read.

Collect the rest of the properties

const user = { name: "Riya", age: 20, city: "Delhi", country: "India" };

const { name, ...otherDetails } = user;

console.log(name);           // "Riya"
console.log(otherDetails);   // { age: 20, city: "Delhi", country: "India" }

Destructuring into existing variables

If the variables already exist, wrap the whole line in round brackets. Otherwise JavaScript thinks { starts a code block:

let name, age;

({ name, age } = { name: "Riya", age: 20 });

console.log(name, age);   // "Riya" 20

Destructuring null or undefined is an error

try {
  const { name } = undefined;
} catch (error) {
  console.log(error.name);   // "TypeError"
}

A safe habit is to give a default object: const { name } = user || {};.

Destructuring in function parameters

You can destructure right in the parameter list. This is one of the most useful tricks, especially for functions that take many options.

// Without destructuring
function showUser(user) {
  console.log(user.name + " is " + user.age);
}

// With destructuring
function showUser2({ name, age }) {
  console.log(`${name} is ${age}`);
}

showUser2({ name: "Riya", age: 20 });   // "Riya is 20"

Add defaults, and make the whole parameter optional with = {}:

function createButton({ text = "Click", color = "blue", size = "medium" } = {}) {
  return `${size} ${color} button saying "${text}"`;
}

console.log(createButton({ text: "Buy now", color: "green" }));
// "medium green button saying "Buy now""

console.log(createButton());
// "medium blue button saying "Click""

Callers do not need to remember the order of arguments, only the names. That makes code easier to read: createButton({ color: "red" }) is clearer than createButton(undefined, "red").

In loops and callbacks

const students = [
  { name: "Riya", marks: 82 },
  { name: "Karan", marks: 67 }
];

for (const { name, marks } of students) {
  console.log(`${name}: ${marks}`);
}
// "Riya: 82"
// "Karan: 67"

const names = students.map(({ name }) => name);
console.log(names);   // ["Riya", "Karan"]

With Object.entries (from the last lesson), you unpack each [key, value] pair:

const prices = { pen: 10, bag: 450 };

for (const [item, price] of Object.entries(prices)) {
  console.log(item, price);
}
// "pen" 10
// "bag" 450

Spread syntax ...

The three dots spread (expand) the items of an array or the properties of an object into a new place. Think of pouring everything out of a box.

Spread with arrays

Copy an array:

const original = [1, 2, 3];
const copy = [...original];

copy.push(4);

console.log(original);   // [1, 2, 3]  (unchanged)
console.log(copy);       // [1, 2, 3, 4]

Combine arrays:

const frontend = ["HTML", "CSS", "JavaScript"];
const backend = ["Node.js", "MongoDB"];

const fullStack = [...frontend, ...backend];

console.log(fullStack);   // ["HTML", "CSS", "JavaScript", "Node.js", "MongoDB"]

Add items anywhere without changing the original:

const numbers = [2, 3, 4];

const withStartAndEnd = [1, ...numbers, 5];

console.log(withStartAndEnd);   // [1, 2, 3, 4, 5]
console.log(numbers);           // [2, 3, 4]

Turn text into an array of letters:

console.log([..."Hello"]);   // ["H", "e", "l", "l", "o"]

Pass an array as separate arguments:

const scores = [72, 95, 60];

console.log(Math.max(...scores));   // 95
console.log(Math.min(...scores));   // 60

Math.max wants separate numbers, not an array. ...scores turns [72, 95, 60] into 72, 95, 60.

Spread with objects

Copy an object:

const user = { name: "Riya", age: 20 };
const copy = { ...user };

copy.name = "Karan";

console.log(user.name);   // "Riya"  (unchanged)

Merge objects (later ones win):

const defaults = { theme: "light", fontSize: 16, language: "en" };
const userChoice = { theme: "dark", fontSize: 18 };

const settings = { ...defaults, ...userChoice };

console.log(settings);
// { theme: "dark", fontSize: 18, language: "en" }

Update a property without changing the original:

const user = { name: "Riya", age: 20, city: "Delhi" };

const updated = { ...user, age: 21 };

console.log(updated);   // { name: "Riya", age: 21, city: "Delhi" }
console.log(user.age);  // 20  (original is safe)

Order matters

When keys clash, the last one wins:

const a = { x: 1, y: 2 };

console.log({ ...a, y: 99 });   // { x: 1, y: 99 }  (y is overridden)
console.log({ y: 99, ...a });   // { y: 2, x: 1 }   (a's y overrides 99)

So put the defaults first and the overrides last.

Spread makes a shallow copy

Just like Object.assign, spread copies only the first level. Nested objects and arrays are still shared:

const user = { name: "Riya", address: { city: "Delhi" } };
const copy = { ...user };

copy.address.city = "Mumbai";

console.log(user.address.city);   // "Mumbai"  (changed too!)

To change a nested value safely, spread every level you touch:

const safeCopy = {
  ...user,
  address: { ...user.address, city: "Pune" }
};

console.log(safeCopy.address.city);   // "Pune"
console.log(user.address.city);       // "Mumbai"  (not changed this time)

Spread is not for plain objects in a loop

You cannot spread an object into an array, or an array into the arguments of something that is not iterable:

const obj = { a: 1 };

try {
  const arr = [...obj];
} catch (error) {
  console.log(error.name);   // "TypeError"  (an object is not iterable)
}

console.log({ ...[10, 20] });   // { 0: 10, 1: 20 }  (spreading an array into an object works)

Rest parameters ...

The same three dots, used when you define a function, do the opposite. They collect all the extra arguments into a real array.

function sum(...numbers) {
  return numbers.reduce((total, n) => total + n, 0);
}

console.log(sum(1, 2));              // 3
console.log(sum(10, 20, 30, 40));    // 100
console.log(sum());                  // 0

numbers is a real array, so map, filter and reduce all work on it.

Mix normal parameters with rest

Rest must be the last parameter. Anything before it takes its own argument first:

function introduce(greeting, ...names) {
  return `${greeting}, ${names.join(" and ")}!`;
}

console.log(introduce("Hello", "Riya", "Karan", "Neha"));
// "Hello, Riya and Karan and Neha!"
function wrong(...items, last) { }   // SyntaxError: rest must be last

Rest vs the old arguments object

Older code used a special object called arguments. It looks like an array, but it is not one, and arrow functions do not have it. Rest parameters are cleaner:

const total = (...numbers) => numbers.reduce((sum, n) => sum + n, 0);

console.log(total(5, 10, 15));   // 30

Spread vs rest: how to tell them apart

Both use .... The difference is where you write it:

Rest (collect) Spread (expand)
Where Left side of =, or in the parameter list of a function Right side, inside [ ], { } or a function call
Job Gathers many items into one array or object Opens one array or object into many items
Example function f(...args) and const [a, ...rest] = list [...list, 4] and f(...list)

A simple memory trick:

  • Rest is at the end of a list on the receiving side: “put the rest here.”
  • Spread is on the giving side: “spread these out.”
const numbers = [1, 2, 3, 4];

const [first, ...rest] = numbers;    // REST: collects 2, 3, 4 into rest
const more = [...numbers, 5];        // SPREAD: opens numbers into the new array

console.log(first, rest);   // 1 [2, 3, 4]
console.log(more);          // [1, 2, 3, 4, 5]

Real-life use cases

1. Update state without changing the original

This is how React and many state libraries work: never change the old data, create a new copy with the change.

const todos = [
  { id: 1, text: "Learn JS", done: false },
  { id: 2, text: "Build a project", done: false }
];

// Add a todo
const withNew = [...todos, { id: 3, text: "Get a job", done: false }];

// Mark todo 1 as done
const withDone = todos.map((todo) =>
  todo.id === 1 ? { ...todo, done: true } : todo
);

// Remove todo 2
const withoutSecond = todos.filter((todo) => todo.id !== 2);

console.log(withNew.length);       // 3
console.log(withDone[0].done);     // true
console.log(todos[0].done);        // false  (original is safe)
console.log(withoutSecond.length); // 1

2. Remove a secret field before sending data

const user = {
  id: 7,
  name: "Riya",
  email: "riya@example.com",
  password: "secret123"
};

const { password, ...safeUser } = user;

console.log(safeUser);   // { id: 7, name: "Riya", email: "riya@example.com" }

password is pulled out and thrown away, and safeUser keeps everything else.

3. Default options plus user options

function startQuiz(userOptions = {}) {
  const defaults = { questions: 10, timeLimit: 60, showHints: true };
  const options = { ...defaults, ...userOptions };

  console.log(options);
}

startQuiz({ questions: 5 });
// { questions: 5, timeLimit: 60, showHints: true }

4. Read only what you need from an API response

const response = {
  status: "success",
  data: {
    user: { id: 7, name: "Karan" },
    posts: ["Arrays", "Objects"]
  }
};

const { status, data: { user, posts } } = response;

console.log(status);        // "success"
console.log(user.name);     // "Karan"
console.log(posts.length);  // 2

5. A function that returns many values

Functions return one thing, but that thing can be an array or object, which you unpack at once:

function getMinMax(numbers) {
  return [Math.min(...numbers), Math.max(...numbers)];
}

const [min, max] = getMinMax([40, 5, 100, 25]);

console.log(min, max);   // 5 100

React’s useState works the same way: const [count, setCount] = useState(0);.

6. A flexible logger

function log(level, ...messages) {
  console.log(`[${level.toUpperCase()}]`, messages.join(" "));
}

log("info", "Server", "started", "on", "port", 3000);
// [INFO] Server started on port 3000

7. Split a name or a date

const [firstName, ...lastParts] = "Riya Kumari Sharma".split(" ");

console.log(firstName);              // "Riya"
console.log(lastParts.join(" "));    // "Kumari Sharma"

const [year, month, day] = "2026-10-01".split("-");
console.log(day, month, year);       // "01" "10" "2026"

8. Remove duplicates from an array

const tags = ["js", "css", "js", "html", "css"];

const unique = [...new Set(tags)];

console.log(unique);   // ["js", "css", "html"]

9. Pagination: first item and the rest

const [headline, ...otherNews] = ["Big launch", "Sports win", "Weather update"];

console.log(headline);    // "Big launch"
console.log(otherNews);   // ["Sports win", "Weather update"]

10. Swap items in an array

const players = ["Riya", "Karan", "Neha"];

[players[0], players[2]] = [players[2], players[0]];

console.log(players);   // ["Neha", "Karan", "Riya"]

This is the same trick you saw in the shuffle example in the sorting lesson.

Common mistakes

  • Mixing up the brackets. Arrays are unpacked with [ ] by position. Objects are unpacked with { } by name.
  • Using the wrong key name in object destructuring. const { username } = { name: "Riya" } gives undefined, because the key is name.
  • Forgetting that name: newName means “rename”. The left side is the key, and the right side is the new variable.
  • Destructuring into existing variables without round brackets. { a, b } = obj; is a syntax error. Write ({ a, b } = obj);.
  • Destructuring null or undefined. It throws a TypeError. Give it a default: const { a } = obj || {};.
  • Thinking spread makes a deep copy. It copies only the first level. Nested objects are still shared.
  • Putting the overrides before the defaults in { ...userOptions, ...defaults }. The defaults then win. Defaults first, overrides last.
  • Putting a rest parameter anywhere but last. function f(...a, b) is a SyntaxError.
  • Spreading an object into an array ([...obj]). Objects are not iterable. Use Object.values(obj) or Object.entries(obj) first.
  • Using too deep nested destructuring. If the pattern is hard to read, use two steps.
  • Forgetting a semicolon before a line that starts with [ or (, like [a, b] = [b, a].

Practice

  1. Destructure const fruits = ["apple", "banana", "mango"]; into first and second. Print both.
  2. From ["HTML", "CSS", "JS", "React"], take the first item into main and the rest into others.
  3. Swap two variables a = 10 and b = 20 using destructuring.
  4. Destructure const book = { title: "JS Basics", author: "Riya", price: 350 }; into title and price.
  5. Rename author into a variable called writer, and give pages a default of 100.
  6. Write a function printUser({ name, age = 18, city = "Unknown" }) and call it with objects that miss some keys.
  7. Use spread to combine [1, 2, 3] and [4, 5, 6] into one array, and then add 0 at the start and 7 at the end.
  8. Use spread to create updatedBook from book with a new price, without changing book.
  9. Write sum(...numbers) that adds any number of arguments. Then call it with ...[5, 10, 15].
  10. Remove email from { id: 1, name: "Riya", email: "r@example.com" } using rest in object destructuring.
  11. Given const user = { name: "Riya", address: { city: "Delhi" } };, create a copy with the city changed to "Pune" so that the original is not changed.
  12. Challenge: write mergeSettings(...objects) that merges any number of settings objects, where later ones override earlier ones.

Recap

  • Destructuring unpacks values into variables. Arrays are matched by position with [ ]. Objects are matched by name with { }.
  • You can skip items, give default values, rename (key: newName), go nested, and collect the leftovers with ...rest.
  • You can destructure right in function parameters, which makes functions with many options easy to call.
  • Spread (...) expands an array or object. Use it to copy, combine and update without changing the original. Defaults first, overrides last.
  • Spread makes a shallow copy. For nested data, spread each level you change.
  • Rest parameters (function f(...args)) collect extra arguments into a real array, and must be last.
  • The easy way to tell them apart: rest collects, spread expands.
  • Next you will learn value vs reference: why changing a copy sometimes changes the original, and how to avoid it.