Skip to content
Back to blogs
May 8, 2026·Updated May 8, 2026·5 min read

Spread vs Rest Operators in JavaScript

Learn the difference between JavaScript's spread and rest operators. Same syntax, opposite behavior.

Summary

Spread and rest both use `...` but do opposite things. Spread expands an iterable into individual elements. Rest collects individual elements into a single array. This post breaks down each one clearly with real examples.

Spread vs Rest Operators in JavaScript

Here's the thing that trips people up: it's the same three dots (...) doing two completely different jobs depending on where you put them. Think of it like a cardboard box. Spread is emptying the box and laying everything out on the table. Rest is collecting everything scattered on the table and putting it into the box. Same motion, opposite direction.

The Spread Operator: Expanding Values

Spread takes something iterable (an array, a string, an object) and expands it into individual pieces.

┌─────────────────────────────────────┐
│          Spread Operator            │
│                                     │
│  [1, 2, 3]  ──►  ...  ──►  1  2  3  │
│                                     │
│  One thing      expands    many     │
│  (array)         into      pieces   │
└─────────────────────────────────────┘

Spread with Arrays

const fruits = ["apple", "banana"];
const moreFruits = ["mango", ...fruits, "grape"];
 
console.log(moreFruits);
// ["mango", "apple", "banana", "grape"]

Without spread, you'd get a nested array:

const wrong = ["mango", fruits, "grape"];
// ["mango", ["apple", "banana"], "grape"]  ← not what you want

Spread also makes copying arrays clean and explicit:

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]

This is a shallow copy. Primitives are copied by value. Nested objects inside the array are still referenced, not duplicated.

Merging arrays becomes one line:

const a = [1, 2, 3];
const b = [4, 5, 6];
const merged = [...a, ...b];
// [1, 2, 3, 4, 5, 6]

Spread with Objects

Spread works on objects too, available since ES2018:

const defaults = { theme: "light", lang: "en", fontSize: 14 };
const userPrefs = { theme: "dark", fontSize: 16 };
 
const config = { ...defaults, ...userPrefs };
// { theme: "dark", lang: "en", fontSize: 16 }

Later properties overwrite earlier ones. userPrefs.theme overwrites defaults.theme. This pattern is everywhere in React for merging props and state updates.

Copying an object with one modification:

const user = { name: "Atharv", role: "admin", active: true };
const deactivated = { ...user, active: false };
 
// user is unchanged, deactivated has active: false

The Rest Operator: Collecting Values

Rest does the opposite. It gathers multiple values into a single array. The key constraint: rest must always be last.

┌───────────────────────────────────────┐
│           Rest Operator               │
│                                       │
│  1  2  3  4  ──►  ...  ──►  [1,2,3,4] │
│                                       │
│  many pieces    collects   one thing  │
│                  into      (array)    │
└───────────────────────────────────────┘

Rest in Function Parameters

The classic use case: a function that accepts any number of arguments.

function sum(...numbers) {
  return numbers.reduce((total, n) => total + n, 0);
}
 
sum(1, 2, 3); // 6
sum(10, 20, 30, 40); // 100

numbers is a real array, not the old arguments object. You can call map, filter, reduce on it directly.

You can mix fixed parameters with rest:

function logWithPrefix(prefix, ...messages) {
  messages.forEach((msg) => console.log(`[${prefix}] ${msg}`));
}
 
logWithPrefix("INFO", "Server started", "Listening on port 3000");
// [INFO] Server started
// [INFO] Listening on port 3000

prefix captures the first argument. messages collects everything after it. Rest must be last: you can't put fixed parameters after ...rest.

Rest in Destructuring

Rest also works in array and object destructuring to collect what's left over:

// Array destructuring
const [first, second, ...remaining] = [1, 2, 3, 4, 5];
// first = 1, second = 2, remaining = [3, 4, 5]
 
// Object destructuring
const { name, role, ...rest } = {
  name: "Atharv",
  role: "admin",
  active: true,
  plan: "pro",
};
// name = "Atharv", role = "admin"
// rest = { active: true, plan: "pro" }

The object rest pattern is especially useful when you need to extract specific keys and pass the remainder somewhere else:

function createUser({ password, ...safeData }) {
  // password is extracted and handled separately
  // safeData = everything except password
  saveToDatabase(safeData);
}

Spread vs Rest: Side by Side

Same syntax. Context determines the job.

┌───────────────────────────────────────────────────────────┐
│                  Where you see ...                        │
├─────────────────────────────┬─────────────────────────────┤
│           Spread            │            Rest             │
├─────────────────────────────┼─────────────────────────────┤
│ On the right side of =      │ On the left side of =       │
│ const b = [...a]            │ const [x, ...rest] = a      │
│                             │                             │
│ In a function call          │ In a function definition    │
│ fn(...args)                 │ function fn(...args) {}     │
│                             │                             │
│ Expanding into a target     │ Collecting into an array    │
│ Many ──► many               │ Many ──► one                │
└─────────────────────────────┴─────────────────────────────┘

The mental shortcut: if ... is producing values into a context (array literal, object literal, function call), it's spread. If ... is receiving values from a context (function parameters, destructuring), it's rest.

Practical Use Cases

Cloning without mutation:

// Prefer this over Object.assign({}, obj)
const updated = { ...existingRecord, updatedAt: new Date() };

Passing array elements as function arguments:

const nums = [5, 1, 8, 3];
Math.max(...nums); // 8
 
// Without spread: Math.max(5, 1, 8, 3) — you'd have to write them out

Omitting keys from an object:

const { password, __v, ...publicUser } = fullUserDocument;
// publicUser has everything except password and __v

Flexible API functions:

function applyMiddleware(app, ...middlewares) {
  middlewares.forEach((mw) => app.use(mw));
}
 
applyMiddleware(app, cors(), helmet(), rateLimiter);

Key Takeaways

  • Spread and rest use the same ... syntax but do opposite things.
  • Spread expands an iterable into individual elements. Use it in function calls, array literals, and object literals.
  • Rest collects individual values into a single array. Use it in function parameters and destructuring.
  • The position tells you which one it is: producing values (spread) vs collecting values (rest).
  • Object spread is a clean way to copy and override properties without mutating the original.
  • Rest in destructuring lets you extract specific keys and bundle the remainder.

Once the expanding-vs-collecting mental model clicks, you'll start seeing these patterns everywhere in modern JavaScript. They're in React props, Express middleware chains, utility functions, and API handlers. At that point the syntax stops being a puzzle and starts being a tool.