Spread & Rest
A practical guide to cloning objects, merging arrays, and handling infinite function arguments.
JavaScript has a syntax that looks identical in two different situations but means the opposite each time. Three dots: .... Depending on where you put them, they either expand something out or collect things in.
When they expand, that's the spread operator. When they collect, that's the rest operator. Same three characters. Context decides which one you're looking at.
Spread pushes values out. Rest pulls values in.
Spread: Expanding Values Out
Spread takes something that contains multiple values, an array or an object, and unpacks them into individual pieces. Think of it as opening a box and laying everything out on the table.
The most common use is combining arrays. You have two lists and you want one. Before spread, you'd reach for .concat() or a loop. With spread, it's a single line.
const fruits = ["apple", "mango"];
const veggies = ["carrot", "spinach"];
// Spread both arrays into a new one
const groceries = [...fruits, ...veggies];
// ["apple", "mango", "carrot", "spinach"]
// You can mix in extra items anywhere
const fullCart = ["bread", ...fruits, "milk"];
// ["bread", "apple", "mango", "milk"]
Spread also makes copying arrays safe. In JavaScript, if you do const b = a with an array, both b and a point to the same array in memory. Change one, you change the other. Spread creates an actual independent copy.
const original = [1, 2, 3];
// This is NOT a copy, both point to the same array
const stillSame = original;
stillSame.push(4);
console.log(original); // [1, 2, 3, 4]
// Spread makes a real copy
const safeCopy = [...original];
safeCopy.push(99);
console.log(original); // [1, 2, 3]
Spread With Objects
Spread works on objects too, and this is where it becomes genuinely useful in everyday code. The pattern shows up constantly when you want to update one property without touching the rest of an object.
const defaultSettings = {
theme: "dark",
fontSize: 14,
language: "en"
};
const userPrefs = { fontSize: 18, language: "hi" };
// Merge: user prefs override defaults
const finalSettings = { ...defaultSettings, ...userPrefs };
// { theme: "dark", fontSize: 18, language: "hi" }
// Update one field without mutating the original
const updated = { ...defaultSettings, theme: "light" };
// { theme: "light", fontSize: 14, language: "en" }
When keys clash, the last one wins. So ...defaultSettings first, then ...userPrefs means user preferences override the defaults. Flip the order and defaults would overwrite users. Position matters.
Rest: Collecting Values In
Rest does the opposite. Instead of spreading values out, it gathers them together. You use it in two places: function parameters and destructuring assignments.
In function parameters, rest collects all the arguments you didn't name explicitly into one array. This is how you write a function that accepts any number of arguments.
// ...scores collects everything after the first argument
function printResults(name, ...scores) {
console.log(`${name}'s scores:`, scores);
}
printResults("User1", 88, 92, 76);
// User1's scores: [88, 92, 76]
printResults("User2", 95);
// User2's scores: [95]
printResults("User3", 70, 85, 90, 88, 77);
// User3's scores: [70, 85, 90, 88, 77]
The rest parameter always has to be last. You can have as many named parameters before it as you want. But once you write ... in the parameter list, nothing else can come after it.
const [first, second, ...remaining] = [10, 20, 30, 40, 50];
console.log(first); // 10
console.log(second); // 20
console.log(remaining); // [30, 40, 50]
// Works with objects too
const { name, ...rest } = { name: "User1", age: 28, city: "Delhi" };
console.log(name); // "User1"
console.log(rest); // { age: 28, city: "Delhi" }
Spread vs Rest
The real question is: how do you tell which one you're looking at? The answer is where the ... appears.
If it's on the right side of an assignment, or inside a function call, or inside an array or object literal, it's spread. It's taking something and expanding it. If it's in a function's parameter definition, or on the left side of a destructuring assignment, it's rest. It's collecting things that haven't been named yet.
| Spread (...) | Rest (...) |
|---|---|
| Appears in function calls, not definitions | Appears in function definitions, not calls |
Appears inside [ ] or { } literals |
Appears on left side of destructuring |
| Takes one thing and expands it into many | Takes many things and collects them into one |
| Works on arrays, objects, and strings | Result is always a real array |
Practical Use Cases
Passing an array as function arguments. Some functions expect individual arguments, not arrays.
const scores = [88, 76, 95, 63];
// Math.max doesn't take arrays, it takes individual numbers
Math.max(scores); // NaN : won't work
Math.max(...scores); // 95 : works
**
Adding an item to an array without mutating.**
const cart = ["shoes", "hat"];
// Add an item without touching the original
const newCart = [...cart, "jacket"];
// ["shoes", "hat", "jacket"]
// cart is still ["shoes", "hat"]
Writing utility functions that accept any number of arguments. Rest makes this clean without needing the old arguments object.
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
sum(1, 2); // 3
sum(10, 20, 30); // 60
sum(5); // 5
sum(); // 0
Stripping one key from an object. A common pattern when you need to pass an object somewhere but want to exclude a specific field, like removing a password before sending user data to the client.
// removing a key via destructuring + rest
const userData = {
id: 1,
name: "User1",
password: "secret123",
role: "admin"
};
// Pull out password, keep everything else
const { password, ...safeData } = userData;
// safeData = { id: 1, name: "User1", role: "admin" }
// password variable holds "secret123" but isn't passed anywhere
What You Get Out Of This
REFERENCES:




