DEV Community

Cover image for Spread vs Rest Operators in JavaScript
Pratham
Pratham

Posted on

Spread vs Rest Operators in JavaScript

Same three dots, two completely different jobs.


JavaScript has this funny thing where the exact same syntax — ... (three dots) — does two opposite things depending on where you use it. In one context, it spreads values out. In another, it collects values together.

When I first encountered this in the ChaiCode Web Dev Cohort 2026, I kept mixing them up. "Wait, is ... expanding or gathering here?" It took a few examples before the distinction clicked. And once it did, I started seeing both operators everywhere — in function parameters, array manipulation, object merging, you name it.

Let me break it down the way I wish someone had explained it to me.


The Core Idea: Expanding vs Collecting

Before we look at any syntax, understand this one concept:

  • Spread = expanding a collection into individual pieces
  • Rest = collecting individual pieces into a collection

That's the entire difference. One unpacks, the other packs.

SPREAD (expanding):
[1, 2, 3]  →  1, 2, 3
"Take this group and spread it out into individual items"

REST (collecting):
1, 2, 3  →  [1, 2, 3]
"Take these individual items and gather them into a group"
Enter fullscreen mode Exit fullscreen mode

Both use ... — the difference is context.


The Spread Operator — Expanding Values

The spread operator takes an iterable (like an array or object) and spreads its contents out into individual elements.

Spread with Arrays

Copying an Array

const original = [1, 2, 3];
const copy = [...original];

console.log(copy); // [1, 2, 3]
console.log(copy === original); // false — it's a new, independent array
Enter fullscreen mode Exit fullscreen mode

Without spread, you might use original.slice() or Array.from(original). Spread is just cleaner.

Merging Arrays

const fruits = ["Apple", "Mango"];
const veggies = ["Carrot", "Spinach"];

const food = [...fruits, ...veggies];
console.log(food); // ["Apple", "Mango", "Carrot", "Spinach"]
Enter fullscreen mode Exit fullscreen mode

Each array gets spread into individual elements, and they all land in the new array. No loops, no concat() — just clean, readable merging.

Adding Elements While Spreading

const numbers = [2, 3, 4];

const extended = [1, ...numbers, 5, 6];
console.log(extended); // [1, 2, 3, 4, 5, 6]
Enter fullscreen mode Exit fullscreen mode

You can mix spread elements with regular values freely.

Visual: Spread Expanding Elements

const nums = [10, 20, 30];

Before spread:   nums = [10, 20, 30]    one package

                        ...nums
                           
After spread:          10, 20, 30        individual values

Usage:  [...nums, 40]    [10, 20, 30, 40]
        Math.max(...nums)    Math.max(10, 20, 30)    30
Enter fullscreen mode Exit fullscreen mode

Spread with Objects

Spread works on objects too — it copies all key-value pairs into a new object.

Copying an Object

const user = { name: "Pratham", age: 22 };
const userCopy = { ...user };

console.log(userCopy); // { name: "Pratham", age: 22 }
console.log(userCopy === user); // false — independent copy
Enter fullscreen mode Exit fullscreen mode

Merging Objects

const defaults = { theme: "light", fontSize: 14, language: "en" };
const userPrefs = { theme: "dark", fontSize: 18 };

const settings = { ...defaults, ...userPrefs };
console.log(settings);
// { theme: "dark", fontSize: 18, language: "en" }
Enter fullscreen mode Exit fullscreen mode

When properties collide, the last one wins. Here, userPrefs.theme ("dark") overwrites defaults.theme ("light"). This is a super common pattern for merging configurations.

Adding/Overriding Properties

const product = { name: "Laptop", price: 75000 };

const updatedProduct = { ...product, price: 65000, inStock: true };
console.log(updatedProduct);
// { name: "Laptop", price: 65000, inStock: true }
Enter fullscreen mode Exit fullscreen mode

The original product object stays untouched. You get a new object with the updated price and the new inStock property.

Spread in Function Calls

You can spread an array as individual arguments to a function:

const scores = [85, 92, 78, 95, 88];

// Without spread — awkward
console.log(Math.max(85, 92, 78, 95, 88)); // 95

// With spread — clean
console.log(Math.max(...scores)); // 95
console.log(Math.min(...scores)); // 78
Enter fullscreen mode Exit fullscreen mode

Math.max() doesn't accept an array — it wants individual numbers. Spread converts the array into exactly that.


The Rest Operator — Collecting Values

The rest operator does the opposite of spread. It collects multiple individual elements into a single array or object.

Rest in Function Parameters

The most classic use case. When you don't know how many arguments a function will receive:

const sum = (...numbers) => {
  let total = 0;
  for (const 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
Enter fullscreen mode Exit fullscreen mode

The ...numbers parameter collects all arguments into an array called numbers. You can pass any number of arguments and they'll all be gathered up.

Visual: Rest Collecting Values

Function call:   sum(10, 20, 30, 40)
                               

Parameter:       (...numbers)
                               
                  numbers = [10, 20, 30, 40]    collected into one array

"Take all these individual values and pack them into an array"
Enter fullscreen mode Exit fullscreen mode

Rest with Regular Parameters

You can combine rest with named parameters — rest just has to come last:

const introduce = (greeting, ...names) => {
  for (const name of names) {
    console.log(`${greeting}, ${name}!`);
  }
};

introduce("Hello", "Pratham", "Arjun", "Priya");
// Hello, Pratham!
// Hello, Arjun!
// Hello, Priya!
Enter fullscreen mode Exit fullscreen mode

greeting gets the first argument ("Hello"), and ...names collects everything else into an array.

Rest in Destructuring — Arrays

We touched on this in the destructuring article, but it's worth repeating:

const scores = [95, 88, 76, 82, 91];

const [first, second, ...rest] = scores;

console.log(first); // 95
console.log(second); // 88
console.log(rest); // [76, 82, 91]
Enter fullscreen mode Exit fullscreen mode

first and second get their values by position. ...rest gathers everything that's left.

Rest in Destructuring — Objects

const user = {
  name: "Pratham",
  age: 22,
  city: "Delhi",
  course: "Web Dev",
  isActive: true,
};

const { name, age, ...details } = user;

console.log(name); // "Pratham"
console.log(age); // 22
console.log(details); // { city: "Delhi", course: "Web Dev", isActive: true }
Enter fullscreen mode Exit fullscreen mode

name and age are extracted. Everything else is collected into details. This is incredibly useful when you need to separate certain properties from the rest.


Spread vs Rest — The Differences

Here's the thing that confuses everyone: they look identical. Both are .... The difference is entirely about where they appear.

Feature Spread ... Rest ...
What it does Expands/unpacks values Collects/gathers values
Direction One → Many Many → One
Where used Array literals, object literals, function calls Function parameters, destructuring
Example [...arr], {...obj}, fn(...arr) (...args), [a, ...rest]
Result Individual elements An array or object

The Simple Rule

If ... appears on the right side of = (or in a function call), it's SPREAD.

If ... appears on the left side of = (or in a function parameter), it's REST.

// SPREAD — right side, expanding
const newArr = [...oldArr];
//              ^^^ spreading oldArr's values into newArr

// REST — left side, collecting
const [first, ...others] = someArray;
//            ^^^ collecting remaining values into others

// SPREAD — in a function call, expanding
console.log(Math.max(...numbers));
//                    ^^^ spreading array into individual arguments

// REST — in a function parameter, collecting
const sum = (...nums) => { /* ... */ };
//           ^^^ collecting arguments into an array
Enter fullscreen mode Exit fullscreen mode

Practical Use Cases

1. Immutable Updates (Don't Mutate the Original)

This is huge in React and modern JavaScript. Instead of modifying an object or array directly, you create a new one with spread:

// Adding an item to an array without mutating
const todos = ["Buy groceries", "Clean room"];
const newTodos = [...todos, "Study JavaScript"];
console.log(newTodos); // ["Buy groceries", "Clean room", "Study JavaScript"]
console.log(todos); // ["Buy groceries", "Clean room"] — unchanged!

// Updating an object property without mutating
const user = { name: "Pratham", age: 22 };
const updatedUser = { ...user, age: 23 };
console.log(updatedUser); // { name: "Pratham", age: 23 }
console.log(user); // { name: "Pratham", age: 22 } — unchanged!
Enter fullscreen mode Exit fullscreen mode

2. Flexible Functions with Rest

const logAll = (label, ...items) => {
  console.log(`--- ${label} ---`);
  items.forEach((item, i) => console.log(`${i + 1}. ${item}`));
};

logAll("My Skills", "JavaScript", "React", "Node.js", "MongoDB");
// --- My Skills ---
// 1. JavaScript
// 2. React
// 3. Node.js
// 4. MongoDB
Enter fullscreen mode Exit fullscreen mode

3. Combining Arrays with Deduplication

const frontend = ["HTML", "CSS", "JavaScript", "React"];
const backend = ["Node.js", "Express", "JavaScript", "MongoDB"];

const allSkills = [...new Set([...frontend, ...backend])];
console.log(allSkills);
// ["HTML", "CSS", "JavaScript", "React", "Node.js", "Express", "MongoDB"]
// JavaScript appears only once!
Enter fullscreen mode Exit fullscreen mode

4. Cloning and Extending API Responses

const apiResponse = {
  id: 101,
  name: "Pratham",
  email: "pratham@prathamdev.in",
};

// Add local metadata without touching the original response
const enriched = {
  ...apiResponse,
  fetchedAt: new Date().toISOString(),
  source: "user-api",
};

console.log(enriched);
// { id: 101, name: "Pratham", email: "pratham@...", fetchedAt: "...", source: "user-api" }
Enter fullscreen mode Exit fullscreen mode

5. Separating Props in Components (React Preview)

// This pattern is everywhere in React
const Button = ({ label, onClick, ...otherProps }) => {
  console.log(label); // "Submit"
  console.log(onClick); // function
  console.log(otherProps); // { className: "btn-primary", disabled: false }
};

Button({
  label: "Submit",
  onClick: () => console.log("clicked"),
  className: "btn-primary",
  disabled: false,
});
Enter fullscreen mode Exit fullscreen mode

Rest destructuring lets you pull out the props you know about and forward the rest — a pattern you'll use constantly in React.


Let's Practice: Hands-On Assignment

Part 1: Spread — Merge and Extend Arrays

const morningTasks = ["Exercise", "Breakfast", "Read news"];
const eveningTasks = ["Cook dinner", "Study", "Sleep"];

const allTasks = [...morningTasks, "Lunch break", ...eveningTasks];
console.log(allTasks);
// ["Exercise", "Breakfast", "Read news", "Lunch break", "Cook dinner", "Study", "Sleep"]
Enter fullscreen mode Exit fullscreen mode

Part 2: Spread — Merge Objects with Override

const baseConfig = {
  theme: "light",
  language: "en",
  notifications: true,
};

const userConfig = {
  theme: "dark",
  fontSize: 18,
};

const finalConfig = { ...baseConfig, ...userConfig };
console.log(finalConfig);
// { theme: "dark", language: "en", notifications: true, fontSize: 18 }
Enter fullscreen mode Exit fullscreen mode

Part 3: Rest — Flexible Function

const average = (...numbers) => {
  const total = numbers.reduce((sum, n) => sum + n, 0);
  return total / numbers.length;
};

console.log(average(80, 90, 70)); // 80
console.log(average(95, 88, 76, 82, 91)); // 86.4
Enter fullscreen mode Exit fullscreen mode

Part 4: Rest in Destructuring

const student = {
  name: "Pratham",
  age: 22,
  course: "Web Dev Cohort 2026",
  city: "Delhi",
  email: "pratham@prathamdev.in",
};

const { name, email, ...academic } = student;
console.log(name); // "Pratham"
console.log(email); // "pratham@prathamdev.in"
console.log(academic); // { age: 22, course: "Web Dev Cohort 2026", city: "Delhi" }
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  1. Spread (...) expands an array or object into individual elements. Use it to copy, merge, and extend collections without mutating the original.
  2. Rest (...) collects multiple individual values into a single array or object. Use it in function parameters and destructuring to handle variable-length inputs.
  3. They look identical — the difference is context. Right side / function call = spread. Left side / function parameter = rest.
  4. Spread is essential for immutable updates — creating new arrays and objects instead of modifying existing ones. This is a core pattern in React.
  5. Both operators are used constantly in modern JavaScript. Array manipulation, object merging, flexible functions, destructuring, React components — they're everywhere.

Wrapping Up

Three dots, two behaviors, infinite use cases. The spread and rest operators are one of those ES6 features that seem simple on the surface but fundamentally change how you write JavaScript. Once the "expanding vs collecting" mental model clicks, you'll find yourself reaching for ... in almost every file you write.

I'm learning all of this through the ChaiCode Web Dev Cohort 2026 under Hitesh Chaudhary and Piyush Garg, and these operators come up in virtually every lesson — especially as we get into React where immutable state updates with spread are the norm. Getting comfortable with them now has already paid off.

Connect with me on LinkedIn or visit PrathamDEV.in to follow along. More articles on the way.

Happy coding! 🚀


Written by Pratham Bhardwaj | Web Dev Cohort 2026, ChaiCode

Top comments (0)