Skip to main content

Command Palette

Search for a command to run...

Spread vs Rest Operators in JavaScript (Key Differences Explained)

Published
16 min read

Introduction

The spread and rest operators are two of the most powerful and frequently used features in modern JavaScript. While they look identical (three dots: ...), they do opposite things and are used in different contexts. Understanding when and how to use each one is crucial for writing efficient, clean JavaScript code. In this blog, we'll explore both operators in detail, see how they differ, and discover their practical applications in real-world code.


1. What Does the Spread Operator Do?

The spread operator (...) expands an iterable (like an array or string) into individual elements. Think of it as unpacking a collection into separate items.

Simple Analogy:

Imagine you have a box of items. The spread operator opens the box and lays out all the items individually so you can see each one.

Basic Concept:

const numbers = [1, 2, 3];

// Without spread operator
console.log(numbers); // [1, 2, 3]

// With spread operator - expands the array into individual elements
console.log(...numbers); // 1 2 3

// It's like writing
console.log(1, 2, 3); // 1 2 3

Notice the difference? Without the spread operator, you get an array. With the spread operator, the elements are separated.

Spread in Function Calls:

const scores = [85, 92, 78, 95];

// Without spread - passes the array as a single argument
Math.max(scores); // NaN - max() expects numbers, not an array

// With spread - passes each element separately
Math.max(...scores); // 95

// It's equivalent to
Math.max(85, 92, 78, 95); // 95

This is one of the most useful applications of the spread operator.

More Examples of Spreading:

const fruits = ["Apple", "Banana"];
const moreFruits = ["Orange", "Mango"];

// Combining arrays without spread
const combined1 = [fruits, moreFruits]; // [["Apple", "Banana"], ["Orange", "Mango"]]

// Combining arrays with spread
const combined2 = [...fruits, ...moreFruits]; // ["Apple", "Banana", "Orange", "Mango"]

console.log(combined2); // ["Apple", "Banana", "Orange", "Mango"]

The spread operator is great for expanding collections.


2. What Does the Rest Operator Do?

The rest operator also uses three dots (...), but it does the opposite of spread. It collects multiple items into a single variable. Think of it as packing individual items into a box.

Simple Analogy:

Imagine you have multiple individual items on a table. The rest operator puts them all into a box so you can handle them as a collection.

Basic Concept:

// Rest operator in function parameters
function sum(...numbers) {
  console.log(numbers); // [1, 2, 3, 4, 5]
  return numbers.reduce((total, num) => total + num, 0);
}

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

Instead of passing an array, you pass individual numbers, and the rest operator collects them into an array.

Rest in Array Destructuring:

const colors = ["Red", "Green", "Blue", "Yellow", "Purple"];

// Without rest - you have to name each variable
const [first, second, third] = colors;

// With rest - collect remaining items
const [first, second, ...remaining] = colors;

console.log(first); // "Red"
console.log(second); // "Green"
console.log(remaining); // ["Blue", "Yellow", "Purple"]

The rest operator collects all remaining elements into an array.

Rest in Object Destructuring:

const person = {
  name: "Ashish",
  age: 21,
  city: "Jaipur",
  email: "ashish@example.com"
};

// Extract name and collect the rest
const { name, ...otherInfo } = person;

console.log(name); // "Ashish"
console.log(otherInfo); // { age: 21, city: "Jaipur", email: "ashish@example.com" }

Very useful for separating important data from the rest.

More Examples:

function logInfo(first, second, ...rest) {
  console.log("First:", first);
  console.log("Second:", second);
  console.log("Rest:", rest);
}

logInfo("A", "B", "C", "D", "E");
// Output:
// First: A
// Second: B
// Rest: ["C", "D", "E"]

3. Differences Between Spread and Rest

While they look identical, spread and rest operators are fundamentally different. Let's understand the key differences.

Difference 1: Direction of Flow

// SPREAD: Unpacking/Expanding (output)
const array = [1, 2, 3];
console.log(...array); // Expands to: 1 2 3

// REST: Packing/Collecting (input)
function collect(...items) {
  console.log(items); // Collects to: [1, 2, 3]
}
collect(1, 2, 3);

Spread operator takes a collection and breaks it apart. Rest operator takes individual items and puts them together.

Difference 2: Where They're Used

// SPREAD is used on the RIGHT side of assignment
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // RIGHT side

// REST is used on the LEFT side of assignment
const [first, ...rest] = [1, 2, 3, 4, 5]; // LEFT side

// Spread in function calls (RIGHT side)
Math.max(...[1, 2, 3]);

// Rest in function parameters (LEFT side)
function sum(...numbers) {}

Pay attention to which side of the = sign it appears on.

Difference 3: Quantity

// SPREAD can appear multiple times
const arr1 = [1, 2];
const arr2 = [3, 4];
const combined = [...arr1, 5, ...arr2, 6]; // Two spreads!
console.log(combined); // [1, 2, 5, 3, 4, 6]

// REST can only appear once (and must be last)
function sum(first, second, ...rest) {} // Correct
function bad(first, ...rest, last) {} // Error! Rest must be last

Rest operator has strict placement rules.

Difference 4: Syntax Context

Operator Context Example
Spread Function calls, Array/Object literals, Array methods [...arr], Math.max(...arr)
Rest Function parameters, Destructuring function(...items), const [a, ...rest]

4. Using Spread with Arrays and Objects

The spread operator is incredibly useful for working with arrays and objects. Let's explore practical applications.

Spread with Arrays:

Copying an Array:

const original = [1, 2, 3];

// Without spread - creates a reference
const copy1 = original;
copy1[0] = 99;
console.log(original); // [99, 2, 3] - Original changed!

// With spread - creates a true copy
const copy2 = [...original];
copy2[0] = 99;
console.log(original); // [1, 2, 3] - Original unchanged
console.log(copy2); // [99, 2, 3]

This is one of the most important uses of spread with arrays.

Combining Arrays:

const team1 = ["Raj", "Priya"];
const team2 = ["Anil", "Sneha"];

// Without spread
const allTeams = [team1, team2]; // [["Raj", "Priya"], ["Anil", "Sneha"]]

// With spread
const allMembers = [...team1, ...team2]; // ["Raj", "Priya", "Anil", "Sneha"]

console.log(allMembers);

Much cleaner than using concat() or push().

Adding Elements:

const scores = [85, 90, 88];

// Add before
const withFirst = [100, ...scores]; // [100, 85, 90, 88]

// Add after
const withLast = [...scores, 92]; // [85, 90, 88, 92]

// Add in the middle
const withMiddle = [85, 90, ...scores, 92]; // Wait, this repeats scores

// Better approach for middle insertion
const list = [1, 2, 5, 6];
const inserted = [1, 2, 3, 4, ...list.slice(2)]; // [1, 2, 3, 4, 5, 6]

Spread with Objects:

Copying an Object:

const original = { name: "Vikram", age: 28, city: "Delhi" };

// Without spread - creates a reference
const copy1 = original;
copy1.age = 30;
console.log(original.age); // 30 - Original changed!

// With spread - creates a shallow copy
const copy2 = { ...original };
copy2.age = 30;
console.log(original.age); // 28 - Original unchanged

Merging Objects:

const user = { name: "Priya", email: "priya@example.com" };
const settings = { theme: "dark", notifications: true };

// Without spread
const merged1 = Object.assign({}, user, settings);

// With spread - cleaner and more readable
const merged2 = { ...user, ...settings };

console.log(merged2);
// { name: "Priya", email: "priya@example.com", theme: "dark", notifications: true }

Overriding Properties:

const defaultSettings = {
  theme: "light",
  fontSize: 14,
  language: "en"
};

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

// Spread defaults first, then override with user settings
const finalSettings = { ...defaultSettings, ...userSettings };

console.log(finalSettings);
// { theme: "dark", fontSize: 16, language: "en" }

Adding Properties:

const car = { brand: "BMW", color: "Black" };

// Add a new property
const updatedCar = { ...car, year: 2020 };

console.log(updatedCar);
// { brand: "BMW", color: "Black", year: 2020 }

// Modify and add
const modifiedCar = { ...car, color: "Red", year: 2021 };

console.log(modifiedCar);
// { brand: "BMW", color: "Red", year: 2021 }

Nested Object Spreading:

const person = {
  name: "Arun",
  address: {
    city: "Bangalore",
    state: "Karnataka"
  }
};

// Shallow spread - address still references original
const copy1 = { ...person };
copy1.address.city = "Mumbai";
console.log(person.address.city); // "Mumbai" - Original changed!

// For deep objects, you need deep copy (spread only does shallow copy)
const copy2 = {
  ...person,
  address: { ...person.address }
};
copy2.address.city = "Mumbai";
console.log(person.address.city); // "Bangalore" - Original unchanged

5. Practical Use Cases

Let's explore real-world scenarios where spread and rest operators shine.

Use Case 1: Function Arguments

// Without rest operator - limited parameters
function addTwo(a, b) {
  return a + b;
}

// With rest operator - flexible parameters
function addAny(...numbers) {
  return numbers.reduce((sum, num) => sum + num, 0);
}

console.log(addAny(1, 2)); // 3
console.log(addAny(1, 2, 3, 4, 5)); // 15
console.log(addAny(10, 20, 30)); // 60

Use Case 2: Copying Arrays for Immutability

const originalList = ["Task 1", "Task 2", "Task 3"];

// Add a new task without modifying original
function addTask(taskList, newTask) {
  return [...taskList, newTask]; // Spread to create new array
}

const updatedList = addTask(originalList, "Task 4");

console.log(originalList); // ["Task 1", "Task 2", "Task 3"]
console.log(updatedList); // ["Task 1", "Task 2", "Task 3", "Task 4"]

Use Case 3: Removing Items from Arrays

const items = ["A", "B", "C", "D", "E"];

// Remove item at index 2 (C)
const [first, second, ...afterRemoved] = items;
const withoutC = [first, second, ...items.slice(3)];

console.log(withoutC); // ["A", "B", "D", "E"]

// Better approach using filter
const filtered = items.filter((item, index) => index !== 2);
console.log(filtered); // ["A", "B", "D", "E"]

Use Case 4: Extracting Headers from Object

const apiResponse = {
  status: 200,
  timestamp: "2026-03-26",
  data: {
    users: [{ id: 1, name: "Raj" }],
    count: 1
  }
};

// Separate metadata from actual data
const { status, timestamp, ...payload } = apiResponse;

console.log(status); // 200
console.log(timestamp); // "2026-03-26"
console.log(payload); // { data: { users: [...], count: 1 } }

Use Case 5: Combining Configuration Objects

const defaultConfig = {
  timeout: 5000,
  retries: 3,
  debug: false
};

const userConfig = {
  timeout: 10000,
  debug: true
};

const finalConfig = { ...defaultConfig, ...userConfig };

console.log(finalConfig);
// { timeout: 10000, retries: 3, debug: true }

Use Case 6: Updating Array Items (Immutable)

const students = [
  { id: 1, name: "Arun", marks: 85 },
  { id: 2, name: "Bhavna", marks: 90 },
  { id: 3, name: "Chirag", marks: 78 }
];

// Update marks for student with id 2 (immutable way)
const updatedStudents = students.map(student =>
  student.id === 2 ? { ...student, marks: 95 } : student
);

console.log(updatedStudents);
// [
//   { id: 1, name: "Arun", marks: 85 },
//   { id: 2, name: "Bhavna", marks: 95 },
//   { id: 3, name: "Chirag", marks: 78 }
// ]

// Original unchanged
console.log(students[1].marks); // 90

Use Case 7: Creating New Objects in React

// Simulating React state update pattern
const user = {
  name: "Sneha",
  age: 25,
  email: "sneha@example.com",
  preferences: {
    theme: "dark",
    notifications: true
  }
};

// Update user name (immutable)
const updatedUser = { ...user, name: "Sneha Sharma" };

// Update nested preference (shallow merge for nested)
const updatedUserPrefs = {
  ...user,
  preferences: { ...user.preferences, theme: "light" }
};

console.log(updatedUserPrefs);
// { name: "Sneha", age: 25, email: "sneha@example.com", preferences: { theme: "light", notifications: true } }

Use Case 8: Passing Multiple Arguments from Array

function greet(greeting, name, punctuation) {
  return `\({greeting}, \){name}${punctuation}`;
}

const args = ["Hello", "Rajesh", "!"];

// Without spread
console.log(greet(args[0], args[1], args[2])); // Hello, Rajesh!

// With spread
console.log(greet(...args)); // Hello, Rajesh!

Use Case 9: Combining Multiple Rest Calls

function buildQuery(method, endpoint, ...options) {
  return {
    method: method,
    endpoint: endpoint,
    options: options
  };
}

const query = buildQuery(
  "GET",
  "/api/users",
  { auth: true },
  { timeout: 5000 },
  { headers: { "Content-Type": "application/json" } }
);

console.log(query);
// {
//   method: "GET",
//   endpoint: "/api/users",
//   options: [
//     { auth: true },
//     { timeout: 5000 },
//     { headers: { "Content-Type": "application/json" } }
//   ]
// }

6. Spread vs Rest: Side-by-Side Comparison

Let's see both operators working together to understand the contrast.

Example 1: Function Definition and Calling

// REST in function definition (packing)
function display(first, ...rest) {
  console.log("First:", first);
  console.log("Rest:", rest);
}

// Call with individual arguments
const args = ["A", "B", "C", "D"];

// SPREAD in function call (unpacking)
display(...args);

// Output:
// First: A
// Rest: [B, C, D]

Example 2: Array Operations

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

// SPREAD to expand arrays
const combined = [...arr1, ...arr2, 7, 8];
console.log(combined); // [1, 2, 3, 4, 5, 6, 7, 8]

// REST to split arrays
const [first, second, ...rest] = combined;
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5, 6, 7, 8]

Example 3: Object Operations

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

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

// REST to extract specific properties
const { a, b, ...remaining } = merged;
console.log(a); // 1
console.log(remaining); // { c: 3, d: 4, e: 5 }

7. Common Patterns in Modern JavaScript

Pattern 1: Clone and Modify

const original = { name: "Vikram", role: "Admin", status: "Active" };

// Create a copy with one property changed
const modified = { ...original, role: "User" };

console.log(original); // { name: "Vikram", role: "Admin", status: "Active" }
console.log(modified); // { name: "Vikram", role: "User", status: "Active" }

Pattern 2: Destructure and Re-spread

const data = {
  id: 1,
  name: "Product",
  price: 100,
  discount: 10,
  inStock: true,
  reviews: 50
};

// Extract important fields, pass rest to another function
const { id, name, ...productDetails } = data;

function updateProductDetails(details) {
  console.log(details);
}

updateProductDetails(productDetails);
// { price: 100, discount: 10, inStock: true, reviews: 50 }

Pattern 3: Adding Optional Parameters

function createUser(name, email, ...optionalData) {
  return {
    name,
    email,
    ...Object.fromEntries(
      optionalData.map((data, i) => [
        i % 2 === 0 ? "key" + (i / 2) : null,
        data
      ])
    )
  };
}

// Or simpler:
function createUserSimple(name, email, metadata = {}) {
  return { name, email, ...metadata };
}

const user = createUserSimple("Priya", "priya@example.com", {
  age: 25,
  city: "Delhi",
  role: "Admin"
});

console.log(user);
// { name: "Priya", email: "priya@example.com", age: 25, city: "Delhi", role: "Admin" }

Pattern 4: Combining Rest and Spread

function processData(first, second, ...rest) {
  const combined = [first, second, ...rest];
  return combined;
}

const data = [1, 2, 3, 4, 5];
const result = processData(...data);

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

8. Visual Guide: Spread vs Rest

Spread: Expanding (Unpacking)

Array:        [1, 2, 3]
              |
         With spread ...
              |
Result:   1, 2, 3 (individual elements)

Object:   { a: 1, b: 2 }
              |
         With spread ...
              |
Result:   a: 1, b: 2 (individual properties)

Rest: Collecting (Packing)

Function: function(a, b, c, d, e)
              |
         With rest ...
              |
Rest gets:   [c, d, e] (remaining as array)

Array:    [1, 2, 3, 4, 5]
              |
         With rest ...
              |
Rest gets:   [3, 4, 5] (remaining as array)

9. Performance Considerations

Spread Creates Shallow Copies:

// Shallow copy - nested objects still reference original
const original = { user: { name: "Raj", age: 25 } };
const copy = { ...original };

copy.user.age = 30;
console.log(original.user.age); // 30 - Changed!

// For deep copy, you need recursive spreading
const deepCopy = {
  ...original,
  user: { ...original.user }
};

deepCopy.user.age = 30;
console.log(original.user.age); // 25 - Unchanged

Rest Creates Arrays:

// Rest always creates arrays, even with single items
function test(...args) {
  console.log(Array.isArray(args)); // true
}

test(); // args = []
test(1); // args = [1]
test(1, 2, 3); // args = [1, 2, 3]

10. Real-World Example: Building a Complete Function

Let's create a practical example that uses both operators:

// A function to process and merge user data

function createUserProfile(userId, userName, ...additionalInfo) {
  // Rest operator collects additionalInfo into an array
  
  const baseProfile = {
    id: userId,
    name: userName,
    createdAt: new Date().toISOString()
  };
  
  // Extract first two additional items
  const [role = "user", status = "active", ...extra] = additionalInfo;
  
  // Spread to combine objects
  const finalProfile = {
    ...baseProfile,
    role,
    status,
    metadata: extra.length > 0 ? { extra } : {}
  };
  
  return finalProfile;
}

// Using the function
const profile1 = createUserProfile(1, "Arun");
console.log(profile1);
// {
//   id: 1,
//   name: "Arun",
//   createdAt: "2026-03-26T...",
//   role: "user",
//   status: "active",
//   metadata: {}
// }

const profile2 = createUserProfile(2, "Bhavna", "admin", "verified", "premium-member");
console.log(profile2);
// {
//   id: 2,
//   name: "Bhavna",
//   createdAt: "2026-03-26T...",
//   role: "admin",
//   status: "verified",
//   metadata: { extra: ["premium-member"] }
// }

Another practical example with data transformation:

// Combining and transforming data from multiple sources

const apiData = {
  status: 200,
  data: {
    user: {
      id: 5,
      name: "Sneha",
      email: "sneha@example.com"
    }
  }
};

const cacheData = {
  lastFetch: "2026-03-26",
  version: "v1"
};

// Extract what we need and merge
const { data: { user }, ...apiMetadata } = apiData;
const finalData = {
  ...user,
  ...cacheData,
  ...apiMetadata
};

console.log(finalData);
// {
//   id: 5,
//   name: "Sneha",
//   email: "sneha@example.com",
//   lastFetch: "2026-03-26",
//   version: "v1",
//   status: 200
// }

Conclusion

The spread and rest operators are essential tools in modern JavaScript. Here's what you need to remember:

Spread Operator (...):

  • Expands iterables into individual elements

  • Used on the right side of assignments

  • Can appear multiple times

  • Useful for: copying arrays/objects, combining collections, passing arguments

Rest Operator (...):

  • Collects multiple elements into a single variable

  • Used on the left side of assignments (parameters, destructuring)

  • Can only appear once and must be last

  • Useful for: flexible function parameters, collecting remaining values

Key Differences:

  • Spread unpacks, Rest packs

  • Spread on right, Rest on left

  • Spread can repeat, Rest can't

Both operators make your code more concise, readable, and functional. They're widely used in modern JavaScript frameworks and libraries, so mastering them is crucial for any JavaScript developer.

Start using these operators in your daily code, and you'll find they quickly become indispensable. They help you write cleaner, more maintainable code that's easier to understand at a glance.