Spread vs Rest Operators in JavaScript
Let's go through spread and rest operators

Frontend Developer ๐ป | Fueled by curiosity and Tea โ | Always learning and exploring new technologies.
Here's something that confuses almost every JavaScript beginner: there are two operators that look exactly the same both are just three dots ... but they do completely opposite things.
One expands things out. The other collects things in.
By the end of this, you'll look at ... and immediately know which one it is and what it's doing. Let's go.
The Three Dots ...
Before we split them apart, let's establish what they share.
Both the spread operator and the rest operator are written as ... three dots, nothing more. JavaScript figures out which one you mean based entirely on where you use it.
Think of it like the word "run." The same word means completely different things in "I run every morning" and "a run of bad luck." Context does all the work. Same idea here.
Spread
Expanding Things Out. The spread operator takes something that's grouped together like an array or object and expands its contents out individually.
Imagine you have a box of apples. Spread tips the box over and lays every apple out on the table individually. The box is gone. Just apples.
const fruits = ["apple", "mango", "banana"];
console.log(fruits); // ["apple", "mango", "banana"] โ the box
console.log(...fruits); // apple mango banana โ apples on table
That's it. ...fruits says don't give me the array, give me everything inside it.
Rest
Collecting Things In. The rest operator does the exact opposite. It takes a bunch of individual values and collects them into an array.
If spread tips the box over, rest is the box itself gathering loose items and packaging them together.
function collect(...items) {
console.log(items); // ["apple", "mango", "banana"]
}
collect("apple", "mango", "banana");
You passed three separate strings. Rest collected them into one neat array called items.
Spread with Arrays
This is where spread gets genuinely useful. Here are the four things you'll do with it all the time.
Copying an array:
const original = [1, 2, 3];
const copy = [...original];
copy.push(4);
console.log(original); // [1, 2, 3] โ untouched
console.log(copy); // [1, 2, 3, 4]
Without spread, doing const copy = original just creates another pointer to the same array. Change one, change both. Spread makes a real independent copy.
Merging arrays:
const frontend = ["HTML", "CSS", "JavaScript"];
const backend = ["Node.js", "Express", "MongoDB"];
const fullStack = [...frontend, ...backend];
// ["HTML", "CSS", "JavaScript", "Node.js", "Express", "MongoDB"]
Clean and readable. No loops. No .concat().
Adding items while spreading:
const skills = ["CSS", "JavaScript"];
const updatedSkills = ["HTML", ...skills, "React"];
// ["HTML", "CSS", "JavaScript", "React"]
You can place ... anywhere in the array literal. Items before, after, or both.
Passing array items as function arguments:
const nums = [4, 8, 2, 6];
console.log(Math.max(nums)); // NaN โ Math.max wants numbers, not an array
console.log(Math.max(...nums)); // 8 โ spread unpacks them perfectly
Math.max expects individual numbers like Math.max(4, 8, 2, 6). Spread converts your array into exactly that.
Spread with Objects
Spread works on objects too โ and it's used constantly in modern JavaScript, especially in React.
Copying an object:
const user = { name: "Satpal", age: 25 };
const copy = { ...user };
copy.age = 30;
console.log(user.age); // 25 โ original untouched
console.log(copy.age); // 30
Merging objects:
const defaults = { theme: "light", language: "en", fontSize: 14 };
const userPrefs = { theme: "dark", fontSize: 18 };
const settings = { ...defaults, ...userPrefs };
// { theme: "dark", language: "en", fontSize: 18 }
When keys clash, the last one wins. userPrefs.theme overwrote defaults.theme. This pattern โ merge defaults, then override with user values is everywhere in real code.
Updating one field without mutating:
const user = { name: "Riya", age: 24, city: "Mumbai" };
const updated = { ...user, age: 25 };
// { name: "Riya", age: 25, city: "Mumbai" }
You spread all existing properties, then write the one you want to change. The original user object is never touched. This is the standard pattern in React state updates.
Rest
Collecting Function Arguments. Rest shines in functions. It lets you accept any number of arguments without knowing in advance how many there will be.
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
sum(1, 2); // 3
sum(1, 2, 3, 4, 5); // 15
sum(10, 20); // 30
...numbers scoops up every argument passed in and packages them into an array. Then you can use all your normal array methods on them.
Mix regular parameters with rest:
function introduce(greeting, ...names) {
names.forEach(name => {
console.log(greeting + ", " + name + "!");
});
}
introduce("Hello", "Priya", "Arjun", "Dev");
// Hello, Priya!
// Hello, Arjun!
// Hello, Dev!
greeting catches the first argument normally. ...names catches everything after it. The rest parameter must always come last nothing can follow it.
Difference:
What it does:
Quick Rule:
The Quick Reference
| Spread | Rest | |
|---|---|---|
| Symbol | ... |
... |
| Direction | Expands out | Collects in |
| Used in | Array literals, object literals, function calls | Function parameter lists |
| Input | Array / object / iterable | Individual arguments |
| Output | Individual values | An array |
| Must be last? | No | Yes, always last parameter |
| Works with objects? | Yes | No, only in destructuring |






