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.
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" }givesundefined, because the key isname. - Forgetting that
name: newNamemeans “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
nullorundefined. It throws aTypeError. 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 aSyntaxError. - Spreading an object into an array (
[...obj]). Objects are not iterable. UseObject.values(obj)orObject.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
- Destructure
const fruits = ["apple", "banana", "mango"];intofirstandsecond. Print both. - From
["HTML", "CSS", "JS", "React"], take the first item intomainand the rest intoothers. - Swap two variables
a = 10andb = 20using destructuring. - Destructure
const book = { title: "JS Basics", author: "Riya", price: 350 };intotitleandprice. - Rename
authorinto a variable calledwriter, and givepagesa default of100. - Write a function
printUser({ name, age = 18, city = "Unknown" })and call it with objects that miss some keys. - Use spread to combine
[1, 2, 3]and[4, 5, 6]into one array, and then add0at the start and7at the end. - Use spread to create
updatedBookfrombookwith a newprice, without changingbook. - Write
sum(...numbers)that adds any number of arguments. Then call it with...[5, 10, 15]. - Remove
emailfrom{ id: 1, name: "Riya", email: "r@example.com" }using rest in object destructuring. - Given
const user = { name: "Riya", address: { city: "Delhi" } };, create a copy with the city changed to"Pune"so that the original is not changed. - 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.