Search
JavaScript Destructuring, Spread and Rest Operators Explained with Examples
If you have read modern JavaScript code, in React components, Node.js APIs or even in the browser console, you have probably seen lines like const { name, email } = user; or [...items, newItem]. Those three dots and curly braces are destructuring, the spread operator and rest parameters. They were added in ES6 (ECMAScript 2015) and ES2018, and today they appear in almost every JavaScript project.
In this tutorial you will learn what each feature does, when to use it, and the one mistake that confuses many beginners: the difference between a shallow copy and a deep copy. Every example below was run on Node.js 24 and Node.js 22, and the output shown is the real console output. The same code also works in all modern browsers.
Requirements
- Node.js 18 or newer to run the examples from the terminal, or any modern browser console (press F12 and open the Console tab).
- A code editor such as VS Code.
- Basic JavaScript: variables, arrays, objects and functions.
Step 1: Array Destructuring
Array destructuring lets you unpack values from an array into separate variables by position. Instead of writing const first = scores[0]; and const second = scores[1];, you write one line. Create array-destructuring.js:
- // Array destructuring: unpack values by position
- const scores = [95, 88, 79, 64];
- const [first, second] = scores;
- console.log("first:", first, "second:", second);
- // Skip items with an empty slot
- const [, , third] = scores;
- console.log("third:", third);
- // Default values are used when the item is undefined
- const [a, b, c = "N/A"] = ["Ana", "Ben"];
- console.log("c:", c);
- // Swap two variables without a temporary variable
- let x = 1;
- let y = 2;
- [x, y] = [y, x];
- console.log("x:", x, "y:", y);
- // Destructure a function that returns an array
- function minMax(numbers) {
- return [Math.min(...numbers), Math.max(...numbers)];
- }
- const [lowest, highest] = minMax(scores);
- console.log("lowest:", lowest, "highest:", highest);
- // Destructure inside a for...of loop
- const grades = new Map([["Ana", 95], ["Ben", 88]]);
- for (const [name, grade] of grades) {
- console.log(`${name} got ${grade}`);
- }
Run it with node array-destructuring.js. Output:
first: 95 second: 88 third: 79 c: N/A x: 2 y: 1 lowest: 64 highest: 95 Ana got 95 Ben got 88
Here is what is going on:
const [first, second] = scores;takes the first two items. Extra items are simply ignored.- An empty slot like
[, , third]skips positions. c = "N/A"is a default value. It is used only when the value isundefined, here because the array has just two items.[x, y] = [y, x]swaps two variables in one line, without a temporary variable.- Destructuring works anywhere you get an array, including function results and
for...ofloops over aMap, where each entry is a[key, value]pair.
Step 2: Object Destructuring
Object destructuring unpacks properties by name, so the order does not matter. This is the one you will use the most, especially with API responses and function parameters. Create object-destructuring.js:
- // Object destructuring: unpack values by property name
- const student = {
- id: 2026001,
- name: "Carla Santos",
- course: "BSIT",
- address: { city: "Cebu City", zip: "6000" },
- };
- const { name, course } = student;
- console.log(name, "-", course);
- // Rename a property while destructuring
- const { id: studentId } = student;
- console.log("studentId:", studentId);
- // Default value for a missing property
- const { year = 1, course: program = "Undeclared" } = student;
- console.log("year:", year, "program:", program);
- // Nested destructuring
- const { address: { city } } = student;
- console.log("city:", city);
- // Destructure function parameters (great for "options" objects)
- function createAccount({ username, role = "student", active = true }) {
- return `${username} | role=${role} | active=${active}`;
- }
- console.log(createAccount({ username: "carla" }));
- console.log(createAccount({ username: "admin", role: "admin", active: false }));
- // Destructure each item of an array of objects
- const users = [
- ];
- users.forEach(({ username, email }) => console.log(`${username} <${email}>`));
Run it with node object-destructuring.js. Output:
Carla Santos - BSIT studentId: 2026001 year: 1 program: BSIT city: Cebu City carla | role=student | active=true admin | role=admin | active=false ana <[email protected]> ben <[email protected]>
Some patterns worth remembering:
- Renaming:
{ id: studentId }reads theidproperty into a variable calledstudentId. Read it as "takeidand call itstudentId". - Defaults:
{ year = 1 }uses 1 because the object has noyear. You can rename and set a default at the same time, likecourse: program = "Undeclared". - Nested objects:
{ address: { city } }goes one level deeper. Note that this creates acityvariable, not anaddressvariable. Ifaddresscould be missing, this line throws aTypeError, so give it a default:{ address: { city } = {} }. - Function parameters:
createAccount({ username, role = "student", active = true })is a clean way to accept "options". Callers pass an object, the order of properties does not matter, and missing options get defaults.
Step 3: The Spread Operator
The spread operator ... does the opposite of destructuring: it expands an array, string, Set or object into individual items. Create spread.js:
- // The spread operator (...) expands an array or object
- const bsit = ["Ana", "Ben"];
- const bscs = ["Carla", "Dan"];
- // Combine arrays
- const allStudents = [...bsit, ...bscs, "Ella"];
- console.log(allStudents);
- // Copy an array (the original is not changed)
- const sorted = [...allStudents].sort().reverse();
- console.log("sorted copy:", sorted);
- console.log("original: ", allStudents);
- // Pass array items as separate arguments
- const prices = [120, 45.5, 300, 89];
- console.log("max price:", Math.max(...prices));
- // Turn a string or Set into an array
- console.log([..."SQL"]);
- console.log("unique:", [...new Set([3, 1, 3, 2, 1])]);
- // Copy and merge objects; later properties win
- const defaults = { theme: "light", pageSize: 10, language: "en" };
- const saved = { theme: "dark", pageSize: 25 };
- const settings = { ...defaults, ...saved };
- console.log(settings);
- // Update one property immutably (common in React state)
- const product = { id: 7, name: "USB Cable", stock: 12 };
- const updated = { ...product, stock: product.stock - 1 };
- console.log(product.stock, "->", updated.stock);
- // Watch out: spread makes a SHALLOW copy
- const original = { name: "Ana", tags: ["honor"] };
- const copy = { ...original };
- copy.name = "Ana R.";
- copy.tags.push("athlete");
- console.log(original.name, original.tags);
- const deep = structuredClone(original);
- deep.tags.push("officer");
- console.log(original.tags, deep.tags);
Run it with node spread.js. Output:
[ 'Ana', 'Ben', 'Carla', 'Dan', 'Ella' ]
sorted copy: [ 'Ella', 'Dan', 'Carla', 'Ben', 'Ana' ]
original: [ 'Ana', 'Ben', 'Carla', 'Dan', 'Ella' ]
max price: 300
[ 'S', 'Q', 'L' ]
unique: [ 3, 1, 2 ]
{ theme: 'dark', pageSize: 25, language: 'en' }
12 -> 11
Ana [ 'honor', 'athlete' ]
[ 'honor', 'athlete' ] [ 'honor', 'athlete', 'officer' ]
Spread is the modern way to:
- Combine arrays:
[...bsit, ...bscs, "Ella"]instead ofconcat(). - Copy before changing:
sort()andreverse()change the original array. Sorting a copy with[...allStudents].sort()keeps the original order safe, as the output shows. - Pass array items as arguments:
Math.max(...prices)works becauseMath.maxexpects separate numbers, not an array. - Remove duplicates:
[...new Set(array)]is a popular one-liner. - Merge objects: in
{ ...defaults, ...saved }the later object wins, so saved settings override the defaults while the missing ones (likelanguage) are kept. - Update without mutating:
{ ...product, stock: product.stock - 1 }creates a new object and leaves the original untouched. This is exactly how state updates are written in React.
Shallow Copy vs Deep Copy
Look closely at the last part of the output. Changing copy.name did not affect original.name, but pushing to copy.tags did change original.tags. That is because spread makes a shallow copy: the top-level properties are copied, but nested arrays and objects are still shared between the two. When you need a fully independent copy, use structuredClone(), which is built into modern browsers and Node.js 17 and newer. After the deep copy, adding "officer" only changed the clone.
Step 4: Rest Parameters and Rest Properties
Rest uses the same three dots, but it collects items instead of expanding them. A simple way to tell them apart: on the right side of = or in a function call it is spread, while in a function's parameter list or on the left side of a destructuring it is rest. Create rest.js:
- // Rest syntax (...) collects the "remaining" items
- // 1. Rest parameters: accept any number of arguments
- function average(...numbers) {
- if (numbers.length === 0) return 0;
- const total = numbers.reduce((sum, n) => sum + n, 0);
- return total / numbers.length;
- }
- console.log(average(90, 85, 77));
- console.log(average());
- // Rest parameter after normal parameters
- function logMessage(level, ...parts) {
- console.log(`[${level.toUpperCase()}]`, parts.join(" "));
- }
- logMessage("info", "User", "ana", "logged in");
- // 2. Rest in array destructuring
- const [winner, ...others] = ["Felix", "Hector", "Gina", "Ivy"];
- console.log("winner:", winner, "others:", others);
- // 3. Rest in object destructuring: remove properties without mutating
- const { password, ...safeUser } = user;
- console.log(safeUser);
- console.log("user still has password:", "password" in user);
Run it with node rest.js. Output:
84
0
[INFO] User ana logged in
winner: Felix others: [ 'Hector', 'Gina', 'Ivy' ]
{ id: 1, username: 'ana', email: '[email protected]' }
user still has password: true
function average(...numbers)accepts any number of arguments and gives you a real array, so you can usereduce,mapand other array methods. It replaces the oldargumentsobject.- A rest parameter must be the last parameter, as in
logMessage(level, ...parts). const [winner, ...others]takes the first item and collects the remaining items in a new array.const { password, ...safeUser } = user;is a very practical trick: it creates a copy of the user without the password, which is what you want before sending user data to the browser or writing it to a log. The original object is not changed.
Quick Comparison
| Syntax | Name | What it does |
|---|---|---|
const [a, b] = arr | Array destructuring | Unpack by position |
const { a, b } = obj | Object destructuring | Unpack by property name |
[...arr], { ...obj }, fn(...arr) | Spread | Expand into separate items or properties |
function f(...args), const [a, ...rest] = arr | Rest | Collect the remaining items into an array or object |
Common Mistakes
- Destructuring
undefinedornull.const { name } = undefined;throws aTypeError. When data may be missing (for example an API response), add a default:const { name } = data ?? {};. - Expecting spread to deep copy. Nested arrays and objects are still shared. Use
structuredClone()when you need a full copy. - Assigning to existing variables with object destructuring. A line that starts with
{is read as a code block, so wrap it in parentheses:({ name, email } = user);. - Putting the rest parameter in the middle.
function f(...items, last)is a syntax error. Rest must come last.
Conclusion
Destructuring, spread and rest make JavaScript code shorter and easier to read: unpack only the values you need, copy and merge arrays and objects without changing the originals, and write functions that accept any number of arguments. Practice by refactoring an older project, for example replacing arguments with rest parameters, or using object destructuring in your function parameters. You can download all four example files below.
Happy coding!