Skip to main content

Command Palette

Search for a command to run...

Spread vs Rest Operators in JavaScript

The Three Dots That Changed Everything ... 🎯

Updated
13 min readView as Markdown
Spread vs Rest Operators in JavaScript
J

Turning chai into code and ideas into full-stack applications. Sharing lessons from my development journey, one commit at a time.

The Pizza Party Paradox 🍕

Last Friday, my team ordered pizza for a hackathon. We had a problem:

The Situation:

  • 5 different pizza boxes

  • 1 giant serving table

  • Everyone wanted to grab slices from all boxes without walking around

Solution 1: The Old Way

// Manually take each pizza out
const pizza1 = ["Pepperoni", "Pepperoni", "Pepperoni"];
const pizza2 = ["Veggie", "Veggie"];
const pizza3 = ["Hawaiian", "Hawaiian", "Hawaiian"];

// Create one array by manually adding each
const allSlices = [];
for (let slice of pizza1) allSlices.push(slice);
for (let slice of pizza2) allSlices.push(slice);
for (let slice of pizza3) allSlices.push(slice);

// Tedious!

Solution 2: The Spread Operator

const allSlices = [...pizza1, ...pizza2, ...pizza3];
// Done! One line!

Then someone said: "Let's divide the remaining slices among the three of us."

The Rest Operator:

const [mySlice, yourSlice, ...leftoverSlices] = allSlices;
// mySlice = first slice
// yourSlice = second slice
// leftoverSlices = ALL the rest

That day, I learned that three little dots (...) can do two completely opposite things depending on context:

  • Spread: Expand/unpack elements (like spreading pizza boxes onto a table)

  • Rest: Collect/pack elements (like gathering leftovers into one box)


The Three Dots: ... - Same Syntax, Opposite Purposes 🎭

Spread Operator: Expanding Values ➡️

Think: "Unpack this collection and spread the items out"

const box = [1, 2, 3];

// WITHOUT spread
console.log(box);        // [1, 2, 3] — one array

// WITH spread
console.log(...box);     // 1 2 3 — three separate values

Visual:

Box:    [1, 2, 3]
         ↓ ...spread
Spread:  1  2  3  (individual items)

Rest Operator: Collecting Values

Think: "Collect all the remaining items into one array"

function sum(...numbers) {
  // numbers COLLECTS all arguments into an array
}

sum(1, 2, 3, 4, 5);
// Inside the function: numbers = [1, 2, 3, 4, 5]

Visual:

Arguments:  1  2  3  4  5  (individual items)
              ↓ ...rest
Rest:       [1, 2, 3, 4, 5]  (one array)

Spread Operator Deep Dive

Case 1: Combining Arrays

const fruits = ["apple", "banana"];
const veggies = ["carrot", "broccoli"];

// Old way: concat
const food1 = fruits.concat(veggies);

// New way: spread
const food2 = [...fruits, ...veggies];

console.log(food2);
// ["apple", "banana", "carrot", "broccoli"]

Why spread is better:

  • More readable

  • Can insert items anywhere

  • Works with multiple arrays

const breakfast = ["eggs", "toast"];
const lunch = ["sandwich"];
const dinner = ["pasta", "salad"];

// Insert in any order!
const meals = [...breakfast, "coffee", ...lunch, "juice", ...dinner];
// ["eggs", "toast", "coffee", "sandwich", "juice", "pasta", "salad"]

Case 2: Copying Arrays (Shallow Copy)

const original = [1, 2, 3];

// Old way: slice
const copy1 = original.slice();

// New way: spread
const copy2 = [...original];

// Modify the copy
copy2.push(4);

console.log(original); // [1, 2, 3] — unchanged 
console.log(copy2);    // [1, 2, 3, 4] — modified 

⚠️ Warning: Shallow Copy Only!

const original = [1, 2, { name: "Alice" }];
const copy = [...original];

// Modifying the nested object affects BOTH
copy[2].name = "Bob";

console.log(original[2].name); // "Bob" 😱
console.log(copy[2].name);     // "Bob" 😱

// The object is shared, not copied!

Why? Spread creates a new array, but the references to objects inside are copied, not the objects themselves.


Case 3: Passing Array Elements as Function Arguments

const numbers = [1, 2, 3];

// Old way: apply
Math.max.apply(null, numbers); // 3

// New way: spread
Math.max(...numbers);          // 3

How it works:

Math.max(...[1, 2, 3])
         ↓
Math.max(1, 2, 3)  // Spreads into separate arguments

More examples:

const dates = [2024, 0, 15]; // Year, Month (0-indexed), Day

// Create a date with spread
const myDate = new Date(...dates);
console.log(myDate); // Mon Jan 15 2024

// Push multiple items
const arr = [1, 2];
arr.push(...[3, 4, 5]);
console.log(arr); // [1, 2, 3, 4, 5]

Case 4: Spreading Objects (ES2018+)

const user = { name: "Alice", age: 25 };
const location = { city: "NYC", country: "USA" };

// Combine objects
const profile = { ...user, ...location };

console.log(profile);
// { name: "Alice", age: 25, city: "NYC", country: "USA" }

Overriding properties:

const defaults = { theme: "dark", fontSize: 14 };
const userSettings = { fontSize: 16 }; // Override fontSize

const settings = { ...defaults, ...userSettings };
console.log(settings);
// { theme: "dark", fontSize: 16 } — userSettings wins!

Order matters!

const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };

// obj2 comes second, its 'b' wins
const result1 = { ...obj1, ...obj2 };
console.log(result1); // { a: 1, b: 3, c: 4 }

// obj1 comes second, its 'b' wins
const result2 = { ...obj2, ...obj1 };
console.log(result2); // { a: 1, b: 2, c: 4 }

Case 5: Adding Properties to Objects

const user = { name: "Bob", age: 30 };

// Add a new property
const userWithId = { ...user, id: 123 };

console.log(userWithId);
// { name: "Bob", age: 30, id: 123 }

// Override existing + add new
const updated = { ...user, age: 31, verified: true };
console.log(updated);
// { name: "Bob", age: 31, verified: true }

Rest Operator Deep Dive

Case 1: Function Parameters (Variadic Functions)

// Old way: arguments object
function oldSum() {
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}

// New way: rest parameters
function sum(...numbers) {
  return numbers.reduce((acc, num) => acc + num, 0);
}

console.log(sum(1, 2, 3));       // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
console.log(sum());              // 0

Why rest is better:

  • numbers is a real array (has .map(), .filter(), etc.)

  • arguments is array-like but NOT an array

  • More readable


Case 2: Collecting Remaining Arguments

function introduce(greeting, ...names) {
  console.log(greeting);
  console.log("People:", names);
}

introduce("Hello", "Alice", "Bob", "Charlie");
// Output:
// Hello
// People: ["Alice", "Bob", "Charlie"]

Rule: Rest parameter MUST be last!

// CORRECT
function func(a, b, ...rest) { }

// WRONG: Rest must be last
function func(...rest, a, b) { } // SyntaxError

Case 3: Array Destructuring

const numbers = [1, 2, 3, 4, 5];

// Get first two, collect the rest
const [first, second, ...rest] = numbers;

console.log(first);  // 1
console.log(second); // 2
console.log(rest);   // [3, 4, 5]

Real-world example:

const [winner, runnerUp, ...participants] = [
  "Alice",
  "Bob",
  "Charlie",
  "Diana",
  "Eve"
];

console.log(winner);       // "Alice"
console.log(runnerUp);     // "Bob"
console.log(participants); // ["Charlie", "Diana", "Eve"]

Case 4: Object Destructuring

const user = {
  id: 1,
  name: "Alice",
  age: 25,
  city: "NYC",
  country: "USA"
};

// Extract specific properties, collect the rest
const { id, name, ...otherDetails } = user;

console.log(id);           // 1
console.log(name);         // "Alice"
console.log(otherDetails); // { age: 25, city: "NYC", country: "USA" }

Practical use: Removing properties

const user = { id: 1, name: "Alice", password: "secret123" };

// Remove password, keep everything else
const { password, ...safeUser } = user;

console.log(safeUser); // { id: 1, name: "Alice" }
// Password is excluded!

Spread vs Rest: The Key Differences

Aspect Spread ... Rest ...
Direction Expands/unpacks Collects/packs
Use in Array literals, object literals, function calls Function parameters, destructuring
Result Individual elements Array (or object)
Example Math.max(...arr) function sum(...nums)
Think of it as Spreading items out Gathering items up

Same syntax, opposite jobs!


Real-World Use Cases

Example 1: Merging State in React

// React component state update
const [state, setState] = useState({
  user: "Alice",
  theme: "dark",
  notifications: true
});

// Update just the theme, keep everything else
setState({
  ...state,
  theme: "light"
});

// Result: { user: "Alice", theme: "light", notifications: true }

Example 2: Building Flexible APIs

function createUser(name, email, ...permissions) {
  return {
    name,
    email,
    permissions,
    createdAt: new Date()
  };
}

const admin = createUser("Alice", "alice@example.com", "read", "write", "delete");
console.log(admin);
// {
//   name: "Alice",
//   email: "alice@example.com",
//   permissions: ["read", "write", "delete"],
//   createdAt: 2026-04-12T...
// }

const guest = createUser("Bob", "bob@example.com", "read");
console.log(guest);
// {
//   name: "Bob",
//   email: "bob@example.com",
//   permissions: ["read"],
//   createdAt: 2026-04-12T...
// }

Example 3: Cloning and Updating Nested Data

const cart = {
  items: [
    { id: 1, name: "Laptop", price: 999 },
    { id: 2, name: "Mouse", price: 25 }
  ],
  total: 1024
};

// Add a new item without mutating original
const updatedCart = {
  ...cart,
  items: [...cart.items, { id: 3, name: "Keyboard", price: 75 }],
  total: cart.total + 75
};

console.log(cart.items.length);        // 2 — original unchanged 
console.log(updatedCart.items.length); // 3 — new cart has the item 

Example 4: Flexible Logger Function

function log(level, ...messages) {
  const timestamp = new Date().toISOString();
  console.log(`[\({timestamp}] [\){level}]`, ...messages);
}

log("INFO", "Server started");
// [2026-04-12T...] [INFO] Server started

log("ERROR", "Database connection failed:", "Timeout after 30s");
// [2026-04-12T...] [ERROR] Database connection failed: Timeout after 30s

Example 5: Removing Duplicates from Arrays

const numbers = [1, 2, 2, 3, 4, 4, 5];

// Combine Set (removes duplicates) with spread
const unique = [...new Set(numbers)];

console.log(unique); // [1, 2, 3, 4, 5]

How it works:

  1. new Set(numbers) — Creates a Set (no duplicates)

  2. ... — Spreads the Set back into an array


Common Mistakes and Gotchas

Mistake 1: Shallow Copy Confusion

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

// Modifying nested array affects BOTH
copy[0].push(99);

console.log(original[0]); // [1, 2, 99] 😱
console.log(copy[0]);     // [1, 2, 99] 😱

Solution: Deep Copy

const deepCopy = JSON.parse(JSON.stringify(original));
// Or use lodash's _.cloneDeep()

Mistake 2: Using Rest in the Middle

// WRONG
function func(...rest, last) { }
// SyntaxError: Rest parameter must be last

// CORRECT
function func(first, ...rest) { }

Mistake 3: Spreading Non-Iterables

const num = 123;

// WRONG
const arr = [...num]; // TypeError: num is not iterable

// CORRECT: Only spread iterables (arrays, strings, Sets, Maps)
const str = "hello";
const chars = [...str]; // ["h", "e", "l", "l", "o"] 

Mistake 4: Expecting Deep Merge

const obj1 = { a: { b: 1 } };
const obj2 = { a: { c: 2 } };

const merged = { ...obj1, ...obj2 };

console.log(merged);
// { a: { c: 2 } } 😱
// Expected: { a: { b: 1, c: 2 } }
// Reality: obj2.a completely REPLACES obj1.a

Why? Spread does shallow merge, not deep merge.

Solution:

const merged = {
  ...obj1,
  a: { ...obj1.a, ...obj2.a }
};

console.log(merged);
// { a: { b: 1, c: 2 } } 

Interview Questions

Q1: What's the difference between spread and rest?

Strong Answer: "Both use the same ... syntax, but in opposite ways. Spread expands an array or object into individual elements—like when you spread an array into function arguments. Rest does the reverse: it collects multiple elements into a single array—like when you gather all remaining function arguments into one parameter. The key is context: spread is used where values are expected (function calls, array literals), while rest is used where variable names are expected (function parameters, destructuring)."


Q2: Can you use spread with objects?

Strong Answer: "Yes, starting with ES2018, spread works with objects too. It creates a shallow copy and merges properties. Later properties override earlier ones. For example, { ...obj1, ...obj2 } creates a new object with all properties from both, with obj2's properties taking precedence if there are conflicts."


Q3: Is spread/rest a shallow or deep copy?

Strong Answer: "Both create shallow copies. For arrays and objects, only the first level is copied. Nested objects or arrays are still referenced from the original. If you need a deep copy, you'll need to either manually copy each level or use utilities like JSON.parse(JSON.stringify()) for simple cases, or lodash's cloneDeep() for more complex structures."


Practice Challenges 🏋️

Challenge 1: Merge and Deduplicate Arrays

const arr1 = [1, 2, 3];
const arr2 = [3, 4, 5];
const arr3 = [5, 6, 7];

// Merge all arrays and remove duplicates

Solution:

const merged = [...arr1, ...arr2, ...arr3];
const unique = [...new Set(merged)];
console.log(unique); // [1, 2, 3, 4, 5, 6, 7]

// One-liner:
const result = [...new Set([...arr1, ...arr2, ...arr3])];

Challenge 2: Create a Flexible Sum Function

// Should work with any number of arguments
sum(1, 2);           // 3
sum(1, 2, 3, 4);     // 10
sum();               // 0

Solution:

function sum(...numbers) {
  return numbers.reduce((acc, num) => acc + num, 0);
}

Challenge 3: Update Nested Object Immutably

const user = {
  id: 1,
  profile: {
    name: "Alice",
    age: 25
  }
};

// Update user.profile.age to 26 WITHOUT mutating original

Solution:

const updatedUser = {
  ...user,
  profile: {
    ...user.profile,
    age: 26
  }
};

console.log(user.profile.age);        // 25 — original unchanged 
console.log(updatedUser.profile.age); // 26 — updated 

The Pizza Party Ending 🍕

Back at the hackathon, after devouring all the pizza, someone asked: "How did we combine all those boxes so easily?"

I smiled. "The spread operator. It's like magic—takes multiple arrays and expands them into one."

"And what about dividing the leftovers?"

"The rest operator. It's the opposite magic—takes multiple items and collects them into one array."

My teammate laughed. "So it's the same three dots, but sometimes it spreads things out, and sometimes it gathers them up?"

"Exactly! Context is everything. That's JavaScript for you—same syntax, opposite behaviors."

Three dots. Two operators. Infinite possibilities.


Key Takeaways

  1. Spread expands (...arr → individual items)

  2. Rest collects (...rest → array)

  3. Same syntax, different contexts

  4. Spread: function calls, array literals, object literals

  5. Rest: function parameters, destructuring

  6. Both create shallow copies — nested objects are referenced

  7. Spread can merge arrays and objects

  8. Rest enables flexible function parameters