Spread and Rest Operators in JavaScript: Expand and Collect with ...

Who is this for? If you have seen
...in JavaScript code and wondered what it means — or why the same three dots seem to do completely different things in different places — this guide explains everything clearly with simple, practical examples.
Table of Contents
Introduction
JavaScript ES6 introduced the ... syntax, and it is one of the most useful additions to the language. The same three dots — ... — do two opposite things depending on where you use them:
... used on an array/object → SPREAD (expand it into individual pieces)
... used in a function param → REST (collect individual pieces into one)
Think of it this way:
Spread is like opening a box and spreading everything out on the table
Rest is like gathering things from the table and putting them back in a box
Both are powerful, both are used constantly in modern JavaScript. Let us learn them one at a time.
💡 Open your browser console (
F12→ Console tab) and try every example as you read.
1. What Does the Spread Operator Do?
The spread operator takes an array (or object) and expands its contents into individual elements. It unpacks the collection.
The core idea
const numbers = [1, 2, 3];
console.log(numbers); // [1, 2, 3] ← the array as one thing
console.log(...numbers); // 1 2 3 ← spread: three separate values
The ... before numbers says: "Do not pass this array as one unit — unpack it and pass each element individually."
Why this matters — a real example
Imagine you want to find the largest number in an array. Math.max() takes individual numbers as arguments — not an array:
// Math.max expects individual values
Math.max(1, 5, 3, 9, 2); // 9 ✓
// Passing an array does NOT work
const scores = [1, 5, 3, 9, 2];
Math.max(scores); // NaN ✗ — Math.max does not accept an array
Without spread, you are stuck. With spread:
const scores = [1, 5, 3, 9, 2];
console.log(Math.max(...scores)); // 9 ✓
// Same as: Math.max(1, 5, 3, 9, 2)
Spread unpacked the array into five separate arguments that Math.max could work with.
Spread with a function call
function greet(firstName, lastName, city) {
console.log("Hello, " + firstName + " " + lastName + " from " + city + "!");
}
const details = ["Priya", "Sharma", "Mumbai"];
greet(...details);
// Same as: greet("Priya", "Sharma", "Mumbai")
// Output: Hello, Priya Sharma from Mumbai!
2. What Does the Rest Operator Do?
The rest operator does the opposite of spread. It collects multiple individual values and bundles them into an array. It packs things together.
The core idea — rest in function parameters
Rest is used in function definitions to collect any number of arguments into a single array:
function sum(...numbers) {
console.log(numbers); // [1, 2, 3, 4, 5] — collected into an array
}
sum(1, 2, 3, 4, 5);
No matter how many arguments you pass, ...numbers collects all of them into one array inside the function.
A working sum function
function sum(...numbers) {
let total = 0;
for (let num of numbers) {
total += num;
}
return total;
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(10, 20, 30, 40)); // 100
console.log(sum(5)); // 5
console.log(sum()); // 0
The same function works for any number of arguments — zero, one, or a hundred. Without rest, you would have to know in advance how many parameters to declare.
Rest after named parameters
Rest collects only the remaining arguments — after any named parameters you have already defined:
function introduce(greeting, ...names) {
console.log(greeting);
console.log("Participants:", names);
}
introduce("Good morning!", "Priya", "Rahul", "Ananya");
// Output:
// Good morning!
// Participants: ["Priya", "Rahul", "Ananya"]
greeting gets the first argument. ...names collects everything that comes after it.
function orderSummary(customerName, ...items) {
console.log("Order for: " + customerName);
console.log("Items ordered:");
items.forEach(function(item) {
console.log(" - " + item);
});
}
orderSummary("Priya", "Laptop", "Mouse", "Keyboard", "Headphones");
// Output:
// Order for: Priya
// Items ordered:
// - Laptop
// - Mouse
// - Keyboard
// - Headphones
⚠️ Important rule: The rest parameter must always be the last parameter in the function definition.
function valid(a, b, ...rest) { } // ✅ rest is last function invalid(...rest, a, b) { } // ❌ SyntaxError — rest must be last
3. Differences Between Spread and Rest
They look identical (...) but work in completely opposite directions. The location in your code tells JavaScript which one you mean.
The core difference
SPREAD → used when CALLING a function or building an array/object
→ takes one collection, EXPANDS into many values
REST → used when DEFINING a function's parameters
→ takes many values, COLLECTS into one array
Side-by-side comparison
// SPREAD — expanding an array into a function call
const nums = [3, 1, 4, 1, 5];
console.log(Math.max(...nums)); // 5
// ↑ spread: unpacking the array here
// REST — collecting arguments inside a function
function getMax(...nums) { // ← rest: gathering them here
return Math.max(...nums); // ← spread: expanding them again
}
console.log(getMax(3, 1, 4, 1, 5)); // 5
Comparison table
Spread ... |
Rest ... |
|
|---|---|---|
| Where used | Function calls, array/object literals | Function parameter definitions |
| What it does | Expands one collection into many values | Collects many values into one array |
| Direction | One → Many | Many → One |
| Works on | Arrays, objects, strings | Function arguments only |
| Result type | Individual values | Array |
The same ... symbol, opposite roles
const fruits = ["mango", "apple", "banana"];
// SPREAD (used in a call) — expanding
console.log(...fruits); // mango apple banana
// REST (used in a definition) — collecting
function listFruits(...fruits) {
console.log(fruits); // ["mango", "apple", "banana"]
}
listFruits("mango", "apple", "banana");
🔑 Simple rule to remember:
Spread is on the right side of
=or inside a function call()Rest is on the left side inside a function definition
()
4. Using Spread with Arrays and Objects
Spread is most commonly used to copy, merge, and build arrays and objects. These patterns appear constantly in modern JavaScript code.
Spread with Arrays
Copying 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]
Without spread, assigning an array creates a reference — both variables point to the same data. With spread, you get a fresh, independent copy.
// Without spread — same array, shared reference
const a = [1, 2, 3];
const b = a;
b.push(99);
console.log(a); // [1, 2, 3, 99] ← original was changed!
// With spread — independent copy
const c = [1, 2, 3];
const d = [...c];
d.push(99);
console.log(c); // [1, 2, 3] ← original stays safe
Merging arrays
const frontend = ["HTML", "CSS", "JavaScript"];
const backend = ["Node.js", "Express", "MongoDB"];
const fullStack = [...frontend, ...backend];
console.log(fullStack);
// ["HTML", "CSS", "JavaScript", "Node.js", "Express", "MongoDB"]
Adding elements while spreading
const base = [2, 3, 4];
const extended = [1, ...base, 5, 6];
console.log(extended); // [1, 2, 3, 4, 5, 6]
You can spread anywhere inside a new array — start, middle, or end.
Spreading a string into characters
const word = "Hello";
const letters = [...word];
console.log(letters); // ["H", "e", "l", "l", "o"]
Spread with Objects
Copying an object
const original = { name: "Priya", age: 22 };
const copy = { ...original };
copy.age = 25;
console.log(original.age); // 22 ← unchanged
console.log(copy.age); // 25
Merging objects
const personalInfo = { name: "Priya", age: 22 };
const contactInfo = { email: "priya@example.com", city: "Mumbai" };
const fullProfile = { ...personalInfo, ...contactInfo };
console.log(fullProfile);
// { name: "Priya", age: 22, email: "priya@example.com", city: "Mumbai" }
Overriding properties when merging
When two objects have the same key, the last one wins:
const defaults = { theme: "light", fontSize: 14, language: "en" };
const userPrefs = { fontSize: 18, language: "hi" };
const settings = { ...defaults, ...userPrefs };
console.log(settings);
// { theme: "light", fontSize: 18, language: "hi" }
// fontSize and language from userPrefs override the defaults
This pattern is extremely common for applying user settings on top of defaults.
Adding or updating a property while spreading
const user = { name: "Priya", role: "member" };
const updated = { ...user, role: "admin", lastLogin: "2025-04-28" };
console.log(updated);
// { name: "Priya", role: "admin", lastLogin: "2025-04-28" }
role: "admin" after the spread overrides the original role: "member".
5. Practical Use Cases
Use case 1 — Passing array values to a function
const temperatures = [28, 35, 22, 40, 19, 31];
const highest = Math.max(...temperatures); // 40
const lowest = Math.min(...temperatures); // 19
console.log("Highest:", highest);
console.log("Lowest: ", lowest);
Use case 2 — Building a flexible logger
function log(level, ...messages) {
console.log("[" + level.toUpperCase() + "]", ...messages);
}
log("info", "Server started on port 3000");
log("error", "Database connection failed:", "timeout after 5s");
log("debug", "User login", "userId:", 42, "role:", "admin");
// Output:
// [INFO] Server started on port 3000
// [ERROR] Database connection failed: timeout after 5s
// [DEBUG] User login userId: 42 role: admin
Use case 3 — Merging and overriding default config
const defaultConfig = {
timeout: 3000,
retries: 3,
verbose: false,
baseUrl: "https://api.example.com"
};
function createClient(userConfig) {
const config = { ...defaultConfig, ...userConfig };
console.log("Config:", config);
return config;
}
createClient({ timeout: 5000, verbose: true });
// Config: { timeout: 5000, retries: 3, verbose: true, baseUrl: "https://api.example.com" }
// ↑ user's timeout and verbose override defaults; retries and baseUrl stay
Use case 4 — Cloning and updating an object without mutation
This pattern is everywhere in React and modern state management:
const student = {
name: "Priya",
age: 21,
course: "Computer Science",
grade: "B"
};
// Update grade without changing the original
const updatedStudent = { ...student, grade: "A" };
console.log(student.grade); // "B" ← original unchanged
console.log(updatedStudent.grade); // "A" ← new object with updated grade
Use case 5 — Collecting remaining items after destructuring
const [first, second, ...remaining] = [10, 20, 30, 40, 50];
console.log(first); // 10
console.log(second); // 20
console.log(remaining); // [30, 40, 50]
And with objects:
const { name, age, ...otherDetails } = {
name: "Rahul",
age: 22,
city: "Delhi",
course: "Mathematics",
grade: "A"
};
console.log(name); // "Rahul"
console.log(age); // 22
console.log(otherDetails); // { city: "Delhi", course: "Mathematics", grade: "A" }
Use case 6 — Combining arrays without duplicates
const set1 = [1, 2, 3, 4];
const set2 = [3, 4, 5, 6];
// Spread both into a Set to remove duplicates, then back to array
const unique = [...new Set([...set1, ...set2])];
console.log(unique); // [1, 2, 3, 4, 5, 6]
Diagrams
Spread — Expanding Elements
┌─────────────────────────────────────────────────────────────┐
│ SPREAD — EXPANDING ELEMENTS │
│ │
│ const scores = [ 85, 92, 78, 96, 88 ] │
│ │ │ │ │ │ │
│ │ │ │ │ │ SPREAD (...) │
│ ▼ ▼ ▼ ▼ ▼ │
│ Math.max(85, 92, 78, 96, 88) │
│ │ │
│ ▼ │
│ Result: 96 │
│ │
│ ───────────────────────────────────────────────────── │
│ │
│ Array copying: │
│ │
│ original = [ 1, 2, 3 ] │
│ │ │ │ │
│ spread ...original │
│ │ │ │ │
│ copy = [ 1, 2, 3 ] ← brand new independent array │
│ │
│ ───────────────────────────────────────────────────── │
│ │
│ Array merging: │
│ │
│ a = [1, 2, 3] b = [4, 5, 6] │
│ │ │ │ │ │ │ │
│ ▼ ▼ ▼ ▼ ▼ ▼ │
│ c = [1, 2, 3, 4, 5, 6] │
│ └─ ...a ─┘ └─ ...b ─┘ │
│ │
│ ✦ Spread OPENS a collection and puts contents out │
│ ✦ Result: many individual values from one source │
└─────────────────────────────────────────────────────────────┘
Rest — Collecting Values
┌─────────────────────────────────────────────────────────────┐
│ REST — COLLECTING VALUES │
│ │
│ function sum(...numbers) { ... } │
│ │
│ Calling: sum( 10, 20, 30, 40, 50 ) │
│ │ │ │ │ │ │
│ │ │ │ │ │ REST (...) │
│ ▼ ▼ ▼ ▼ ▼ │
│ numbers = [ 10, 20, 30, 40, 50 ] │
│ │ │
│ ▼ │
│ total = 150 │
│ │
│ ───────────────────────────────────────────────────── │
│ │
│ Named param + rest: │
│ │
│ function order(customer, ...items) { } │
│ │
│ order( "Priya", "Laptop", "Mouse", "Keyboard" ) │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ customer items = [ "Laptop", "Mouse", "Keyboard" ] │
│ = "Priya" │
│ │
│ ✦ Rest CLOSES individual values into one collection │
│ ✦ Result: one array from many separate arguments │
└─────────────────────────────────────────────────────────────┘
Quick Reference
// ─── SPREAD — expanding ───────────────────────────────────
// Into a function call
Math.max(...[3, 1, 4, 5, 2]); // 5
// Copy an array
const copy = [...original];
// Merge arrays
const merged = [...arr1, ...arr2];
// Add elements
const extended = [0, ...arr, 99];
// Copy an object
const objCopy = { ...original };
// Merge objects
const merged = { ...obj1, ...obj2 };
// Override a property
const updated = { ...obj, key: newValue };
// ─── REST — collecting ────────────────────────────────────
// Collect all arguments
function sum(...nums) {
return nums.reduce((acc, n) => acc + n, 0);
}
// Named param + rest
function greet(title, ...names) {
console.log(title, names);
}
// Rest in destructuring
const [first, ...rest] = [1, 2, 3, 4];
const { name, ...others } = { name: "Priya", age: 22, city: "Mumbai" };
// ─── Key rules ────────────────────────────────────────────
// Spread → used when calling / building (expands outward)
// Rest → used when defining parameters (collects inward)
// Rest parameter must always be last in function definition
Wrapping Up
The ... syntax is small but mighty. Here is everything you learned:
Spread expands a collection (array or object) into its individual parts — used when calling functions, copying arrays/objects, and merging them
Rest collects individual arguments into a single array — used in function parameter definitions to handle any number of arguments
Same syntax, opposite directions: spread goes outward (one → many), rest goes inward (many → one)
The key to telling them apart: spread appears in function calls and literals; rest appears in function definitions
Both are used constantly in modern JavaScript — in React, Node.js, utility functions, and API handling
Once these patterns feel natural, you will start spotting opportunities to use them everywhere — and your code will become noticeably shorter and cleaner.
Happy coding! 🚀
Next step: Look into destructuring with ...rest — it is a natural companion to what you just learned and makes working with arrays and objects even more expressive.
