Skip to main content

Command Palette

Search for a command to run...

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

Updated
14 min read
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

  1. What Does the Spread Operator Do?

  2. What Does the Rest Operator Do?

  3. Differences Between Spread and Rest

  4. Using Spread with Arrays and Objects

  5. Practical Use Cases

  6. Diagrams

  7. Quick Reference


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.