Online Compiler logoOnline Compiler

JavaScript Spread & Rest Operators: Complete Guide

What You'll Learn:

  • ✅ Spread operator syntax and usage
  • ✅ Rest parameters in functions
  • ✅ Array spreading and cloning
  • ✅ Object spreading and merging
  • ✅ Copying vs referencing data
  • ✅ Spread in function calls
  • ✅ When to use spread vs alternatives
  • ✅ Interview questions

What is the Spread Operator?

The spread operator (...) allows iterable objects (like arrays or strings) to be expanded in places where zero or more elements are expected. It's one of the most useful ES6 features, making code cleaner and enabling powerful operations on collections and objects.

Spread in Arrays

Array Spreading Basics

// Spread unpacks array elements
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// Without spread: [[1,2,3], [4,5,6]]
const wrong = [arr1, arr2];
console.log(wrong); // [[1,2,3], [4,5,6]]

// With spread: [1, 2, 3, 4, 5, 6]
const combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]

// Add elements between
const merged = [0, ...arr1, 3.5, ...arr2, 7];
console.log(merged); // [0, 1, 2, 3, 3.5, 4, 5, 6, 7]

// Works with strings (treats as array of characters)
const str = "hello";
const chars = [...str];
console.log(chars); // ['h', 'e', 'l', 'l', 'o']

Spread unpacks array elements inline, useful for combining arrays and adding elements.

Cloning Arrays and Objects

Spread is a great way to create shallow copies of arrays and objects, avoiding unintended mutations.

Cloning with Spread

// Clone an array (shallow copy)
const original = [1, 2, 3];
const clone = [...original];

console.log(clone); // [1, 2, 3]
console.log(clone === original); // false (different arrays)

// Modifying clone doesn't affect original
clone[0] = 99;
console.log(original[0]); // 1 (unchanged)
console.log(clone[0]); // 99

// Clone an object
const originalObj = { name: "Alice", age: 30 };
const cloneObj = { ...originalObj };

console.log(cloneObj); // { name: "Alice", age: 30 }
console.log(cloneObj === originalObj); // false (different objects)

// Modifying clone doesn't affect original
cloneObj.name = "Bob";
console.log(originalObj.name); // "Alice"
console.log(cloneObj.name); // "Bob"

// IMPORTANT: Only shallow copy for nested objects
const nested = { user: { name: "Alice" } };
const shallowClone = { ...nested };

// Nested object is still referenced!
shallowClone.user.name = "Bob";
console.log(nested.user.name); // "Bob" (affected!)

// For deep cloning, use JSON or structured clone
const deepClone = JSON.parse(JSON.stringify(nested));
// Now nested.user.name is unchanged

Spread creates shallow copies. For nested objects, you need deep cloning techniques.

Spread in Objects

Object Spreading

// Merge objects
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const merged = { ...obj1, ...obj2 };
console.log(merged); // { a: 1, b: 2, c: 3, d: 4 }

// Override properties (last one wins)
const base = { x: 1, y: 2, z: 3 };
const override = { y: 20, z: 30 };
const result = { ...base, ...override };
console.log(result); // { x: 1, y: 20, z: 30 }

// Add new properties while keeping existing ones
const user = { name: "Alice", age: 30 };
const updated = { ...user, email: "alice@example.com" };
console.log(updated); // { name: "Alice", age: 30, email: "alice@..." }

// Update specific properties
const modified = { ...user, age: 31 };
console.log(modified); // { name: "Alice", age: 31 }

// Order matters - later values override
const final = { a: 1, ...{ a: 2 }, a: 3 };
console.log(final.a); // 3

Object spread merges objects. Later properties override earlier ones with the same key.

Spread in Function Calls

Spread in Function Arguments

// Pass array elements as separate arguments
function sum(a, b, c) {
  return a + b + c;
}

const nums = [1, 2, 3];
const result = sum(...nums); // Equivalent to sum(1, 2, 3)
console.log(result); // 6

// Math functions
const numbers = [5, 2, 9, 3];
const max = Math.max(...numbers); // Equivalent to Math.max(5, 2, 9, 3)
console.log(max); // 9

// Array methods
const arr1 = [1, 2];
const arr2 = [3, 4];
arr1.push(...arr2); // Push multiple elements
console.log(arr1); // [1, 2, 3, 4]

// Copy arrays
const original = [1, 2, 3];
const copy = Array.from(original); // Alternative
const copy2 = [...original]; // Using spread

// Common: apply method without "apply"
const text = "hello";
const chars = [...text]; // ['h', 'e', 'l', 'l', 'o']

Spread in function calls unpacks array elements as individual arguments.

Rest Parameters vs Spread Operator

Rest parameters are the opposite of spread - they collect multiple arguments into an array. Both use ... but in different contexts.

Rest Parameters

// REST: Collect multiple arguments into array
function sum(...numbers) {
  return numbers.reduce((a, b) => a + b, 0);
}

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

// Rest with other parameters
function greet(greeting, ...names) {
  console.log(`${greeting}, ${names.join(" and ")}!`);
}

greet("Hello", "Alice", "Bob", "Charlie");
// "Hello, Alice and Bob and Charlie!"

// Rest must be last parameter
// function bad(a, ...rest, b) { } // SyntaxError!
function good(a, ...rest) { }

// Destructuring with rest
const [first, ...rest] = [1, 2, 3, 4];
console.log(first); // 1
console.log(rest); // [2, 3, 4]

// In objects
const { x, y, ...others } = { x: 1, y: 2, z: 3, w: 4 };
console.log(x, y); // 1, 2
console.log(others); // { z: 3, w: 4 }

// SPREAD vs REST:
// Rest (...params): Collects arguments INTO an array
// Spread (...array): Expands array INTO arguments

Rest parameters collect multiple arguments. Spread expands arrays. Both use ... but have opposite effects.

Real-World Examples

Practical Spread Usage

// 1. Combine arrays from multiple sources
const oldItems = [1, 2, 3];
const newItems = [4, 5];
const allItems = [...oldItems, ...newItems];

// 2. Create variations without mutation
const config = { timeout: 5000, retries: 3, debug: false };
const prodConfig = { ...config, debug: false };
const devConfig = { ...config, debug: true, timeout: 10000 };

// 3. Add items to immutable array
const todos = [
  { id: 1, text: "Learn JS" },
  { id: 2, text: "Build project" }
];

const newTodo = { id: 3, text: "Deploy app" };
const updatedTodos = [...todos, newTodo];
// Original 'todos' unchanged

// 4. Filter and combine
const arr1 = [1, 2, 3];
const arr2 = [3, 4, 5];
const unique = [...new Set([...arr1, ...arr2])];
console.log(unique); // [1, 2, 3, 4, 5]

// 5. Copy with modifications
const person = { name: "Alice", age: 30 };
const adult = { ...person, isAdult: true };

// 6. Merge multiple objects
const defaults = { theme: "light", language: "en" };
const userPrefs = { theme: "dark" };
const appSettings = { ...defaults, ...userPrefs };
// { theme: "dark", language: "en" }

Spread is crucial for immutable programming, which prevents unintended side effects.

Spread vs Alternatives

When to Use Spread

// Array copying alternatives
const original = [1, 2, 3];

// Method 1: Spread (modern, clean)
const copy1 = [...original];

// Method 2: slice()
const copy2 = original.slice();

// Method 3: Array.from()
const copy3 = Array.from(original);

// Method 4: concat()
const copy4 = original.concat();

// All create shallow copies - prefer SPREAD for readability

// Object merging alternatives
const obj1 = { a: 1 };
const obj2 = { b: 2 };

// Method 1: Spread (modern, clean - PREFERRED)
const merged1 = { ...obj1, ...obj2 };

// Method 2: Object.assign() (older)
const merged2 = Object.assign({}, obj1, obj2);

// Both work, but spread is the modern standard

// Spread is better for:
// ✅ Readability
// ✅ Immutability
// ✅ Easy to chaining operations
// ✅ Works consistently across arrays and objects

Spread is the modern, preferred way for copying and merging. It's cleaner than older alternatives.

Common Pitfalls

Things to Watch For

// PITFALL 1: Shallow copy doesn't deep clone
const nested = { user: { name: "Alice" } };
const copy = { ...nested };
copy.user.name = "Bob";
console.log(nested.user.name); // "Bob" (affected!)

// PITFALL 2: Order matters in object spread
const base = { x: 1, y: 2 };
const override = { x: 10 };
const result1 = { ...base, ...override };
const result2 = { ...override, ...base };
console.log(result1.x); // 10
console.log(result2.x); // 1

// PITFALL 3: Spread creates new array, but content not copied
const arr = [[1, 2], [3, 4]];
const copied = [...arr];
copied[0][0] = 99;
console.log(arr[0][0]); // 99 (affected!)

// PITFALL 4: Rest must be last parameter
// function test(a, ...rest, b) {} // SyntaxError!

// PITFALL 5: Spread is slow for very large arrays
const huge = Array(1000000).fill(0);
const copy = [...huge]; // Creates new array (memory intensive)

Shallow copying, order sensitivity, and nested structures are common spread pitfalls.

Interview Q&A

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

A: Spread (...array) expands array into individual elements. Rest (...params) collects multiple arguments into an array. Spread is used in function calls or array/object literals. Rest is used in function parameters or destructuring.

Q: Why doesn't spread create a deep copy?

A: Spread creates a shallow copy - it copies the first level only. For nested objects/arrays, references are still shared. Deep copying requires recursion or libraries like lodash.deepClone().

Q: Can you use spread in loops?

A: Spread is for immediate expansion, not looping. For loops, use array methods like map(), filter(), or forEach(). However, you can use spread to unpack arrays into rest parameters.

Summary

  • 🎯 Spread (...) expands arrays/objects inline
  • 🎯 Rest (...) collects arguments into arrays
  • 🎯 Great for copying and merging without mutation
  • 🎯 Creates shallow copies only
  • 🎯 Improves code readability vs concatenation
  • 🎯 Works in arrays, objects, and function calls
  • 🎯 Preferred modern alternative to slice/concat/Object.assign