Skip to main content

Command Palette

Search for a command to run...

Spread vs Rest Operators in JavaScript

Published
6 min read
Spread vs Rest Operators in JavaScript

The spread and rest operators are powerful features introduced in ES6 that use the same syntax (...) but serve completely different purposes.

Both are written with three dots ... which is exactly why they confuse so many people. They look identical. But they do opposite things depending on where and how you use them. One expands values out, the other collects values in. Once you internalize that contrast, the two become very easy to tell apart.

Spread = Expands values

Rest = Collects values

Why These Operators Are Important

They help:

  • Simplify code

  • Handle data easily

  • Improve readability

1. What the Spread Operator Does

The spread operator takes an array or object and expands it into individual elements out into a new context.

Think of it like unpacking a suitcase: you take everything that was bundled together and lay each item out separately.

const fruits = ['apple', 'banana', 'mango'];

// spread operator
console.log(...fruits); // apple banana mango
//         ↑ spread unpacks the array into three separate values

Instead of passing the whole array as one thing, spread breaks it apart and hands over each element individually. This becomes very useful when you need to pass array items as separate arguments to a function.

const numbers = [4, 1, 7, 2];

console.log(Math.max(...numbers)); // 7
// Math.max doesn't accept an array — it needs individual values
// spread converts [4, 1, 7, 2] into Math.max(4, 1, 7, 2)

Without spread, you'd have to manually write Math.max(4, 1, 7, 2) or use an older workaround like .apply(). Spread makes this elegant and readable.

2. What the Rest Operator Does

The rest operator does the exact opposite, it collects multiple individual values and bundles them together into a single array. If spread is unpacking a suitcase, rest is packing one.

The most common place you'll see rest is in function parameters, where it gathers all the extra arguments passed into a function:

function sum(...numbers) {
  // `numbers` is now an array of everything passed in
  return numbers.reduce((total, n) => total + n, 0);
}

sum(1, 2, 3);       // 6
sum(10, 20, 30, 40); // 100

Here, no matter how many arguments you pass to sum, the rest operator collects all of them into the numbers array. The function doesn't need to know in advance how many values it will receive, rest handles that flexibility for you.

3. The Difference Between Spread and Rest

Even though they share the same ... syntax, the key to telling them apart is asking: is this expanding values out, or collecting values in?

Spread appears where values are being used, in function calls, array literals, or object literals. It takes something bundled and spreads it open. Rest appears where parameters or variables are being defined, in function signatures or destructuring. It takes loose individual values and wraps them up.

A side-by-side comparison makes this very clear:

// SPREAD — expanding an array into a function call
const nums = [1, 2, 3];
console.log(Math.max(...nums)); // spread at the call site

// REST — collecting arguments inside a function definition
function add(...nums) {        // rest in the parameter list
  return nums.reduce((a, b) => a + b, 0);
}

Both lines use ...nums, but the first is spreading outward and the second is collecting inward. The position call site vs. definition, is what tells you which one it is.

4. Using Spread with Arrays and Objects

Spread really shines when working with arrays and objects, particularly for creating copies and combining data without mutating the originals.

Copying an array without affecting the original:

const original = [1, 2, 3];
const copy = [...original]; // a fresh, independent array

copy.push(4);
console.log(original); // [1, 2, 3] — untouched
console.log(copy);     // [1, 2, 3, 4]

This is important because in JavaScript, assigning an array directly (const copy = original) doesn't create a copy — both variables point to the same array. Spread gives you a true, independent copy.

Merging arrays is equally clean:

const veggies = ['carrot', 'pea'];
const fruits  = ['apple', 'mango'];

const food = [...veggies, ...fruits, 'rice']; // mix and match freely
// ['carrot', 'pea', 'apple', 'mango', 'rice']

Spread works just as naturally with objects. You can copy or merge objects the same way:

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

// Merge: userPrefs values override defaults where they overlap
const settings = { ...defaults, ...userPrefs };
// { theme: 'light', fontSize: 18, language: 'hi' }

Notice that fontSize and language from userPrefs overwrite the values from defaults, while theme — which only exists in defaults — is preserved. This pattern of spreading defaults first and then spreading user-provided values on top is extremely common in real codebases.

5. Practical Use Cases

Knowing the mechanics is one thing, seeing where these operators actually appear in everyday code is what makes them stick. Adding items to an array without mutating it is a very common need, especially in frameworks like React where you're expected to treat data as immutable:

const cart = ['shoes', 'shirt'];

// Add an item by creating a new array instead of using push()
const updatedCart = [...cart, 'hat'];
// ['shoes', 'shirt', 'hat'] — cart itself is unchanged

Passing a dynamic list of arguments to any function that expects individual values:

const dates = [2024, 6, 15]; // year, month, day
const birthday = new Date(...dates); // same as new Date(2024, 6, 15)

Cloning and updating an object in one step, which you'll see constantly in state management:

const user = { name: 'Meera', age: 25, city: 'Pune' };

// Update age without mutating the original user object
const updatedUser = { ...user, age: 26 };
// { name: 'Meera', age: 26, city: 'Pune' }

Building flexible utility functions using rest, so they can accept any number of arguments:

function logWithPrefix(prefix, ...messages) { 
    // prefix is one fixed argument, messages collects the rest
    messages.forEach(msg => console.log([\({prefix}] \){msg})); 
}

logWithPrefix('INFO', 'Server started', 'Listening on port 3000');
// [INFO] Server started
// [INFO] Listening on port 3000

This kind of function is far more flexible than one that accepts a fixed number of arguments. You can pass two messages or ten, it handles all of them gracefully.

Conclusion

Spread and rest are two sides of the same coin. Spread takes a collection and fans it out into individual pieces, useful when you need to pass, copy, or combine data. Rest takes individual pieces and gathers them into a collection, useful when you want a function to handle a variable number of arguments or when you want to capture "the remainder" in a destructuring pattern.

The three dots are the same, but the direction of flow is opposite, and that direction is always determined by context: are you providing values (spread) or receiving them (rest)? Train yourself to ask that question whenever you see ..., and the two will never feel confusing again.