Skip to main content

Command Palette

Search for a command to run...

Spread vs Rest Operators in JavaScript

Updated
13 min read
Spread vs Rest Operators in JavaScript
H
CS undergrad | Tech enthusiast | Focusing on Web Dev • DSA • ML | Building skills for real-world impact

The spread and rest operators look identical: three dots (...). But they do opposite things. One expands values. One collects them. Understand the difference and you'll write cleaner, more powerful code.

This is about what spread and rest do, how they differ, and when to use each one.


Understanding the Three Dots

The ... syntax appears in two different contexts:

Spread:  Takes many elements and spreads them out
Rest:    Takes many values and collects them together

The syntax is the same. The context is different. That's what matters.


What the Spread Operator Does

The spread operator takes an iterable (array, string, or object) and expands it in-place.

Expanding an Array

const arr = [1, 2, 3];

console.log(arr);        // [1, 2, 3]
console.log(...arr);     // 1 2 3

Without spread: The array is printed as an array.

With spread: The elements are printed individually.

Spread Unpacks Values

Without spread:  [1, 2, 3]
                 One array

With spread:     1, 2, 3
                 Three separate values

Real Example: Function Arguments

function add(a, b, c) {
  return a + b + c;
}

const numbers = [1, 2, 3];

// Without spread
add(numbers[0], numbers[1], numbers[2]);  // 6 (clunky)

// With spread
add(...numbers);  // 6 (clean)

Spread takes [1, 2, 3] and expands it to 1, 2, 3. Then those values are passed as separate arguments.

Spread with Math Functions

const numbers = [5, 6, 2, 8, 1];

// Find max without spread
Math.max(numbers[0], numbers[1], numbers[2], numbers[3], numbers[4]); // 8

// With spread
Math.max(...numbers); // 8

Math.max() expects individual arguments, not an array. Spread converts the array to individual arguments.

Spreading Strings

Strings are iterable. You can spread them:

const str = "hello";

console.log(...str);  // h e l l o

const arr = [...str];
console.log(arr);     // ["h", "e", "l", "l", "o"]

Spread expands the string into individual characters.


What the Rest Operator Does

The rest operator collects multiple values into a single array.

Collecting Function Arguments

function sum(...numbers) {
  console.log(numbers);  // An array of all arguments
  let total = 0;
  for (let num of numbers) {
    total += num;
  }
  return total;
}

sum(1, 2, 3);        // numbers = [1, 2, 3]
sum(1, 2, 3, 4, 5);  // numbers = [1, 2, 3, 4, 5]

Rest collects all arguments into a single array numbers.

Rest Collects Values

Without rest:  function (a, b, c) { }
               Can only accept 3 arguments

With rest:     function (...args) { }
               Can accept any number of arguments

Rest with Regular Parameters

function greet(greeting, ...names) {
  console.log(greeting);  // Single value
  console.log(names);     // Array of remaining values
}

greet("Hello", "Alice", "Bob", "Charlie");

// Output:
// "Hello"
// ["Alice", "Bob", "Charlie"]

The first argument goes to greeting. The rest go into the names array.

Rest in Array Destructuring

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

const [first, second, ...rest] = arr;

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

The first two elements are assigned individually. The rest are collected into an array.

Rest in Object Destructuring

const person = {
  name: "John",
  age: 30,
  city: "New York",
  country: "USA"
};

const { name, age, ...location } = person;

console.log(name);      // "John"
console.log(age);       // 30
console.log(location);  // { city: "New York", country: "USA" }

Named properties are extracted. The rest are collected into a new object.


Spread vs Rest: Key Differences

Context Matters

Usage Operator Name Does
Function call ...arr Spread Expands array to arguments
Array literal [...arr] Spread Expands array into new array
Function parameter (...args) Rest Collects arguments into array
Destructuring [a, ...rest] Rest Collects remaining into array

Visual Comparison

SPREAD (Unpacking)
[1, 2, 3]
   ↓
1, 2, 3

REST (Collecting)
1, 2, 3
   ↓
[1, 2, 3]

Spread goes outward. Rest goes inward.

Position Matters

Spread can appear anywhere in an expression:

const a = [1, 2];
const b = [3, 4];

const combined = [0, ...a, ...b, 5];
console.log(combined);  // [0, 1, 2, 3, 4, 5]

Rest can only be the last parameter:

function test(...rest, param) { }  // ERROR
function test(param, ...rest) { }  // CORRECT

Rest must come last because it collects everything remaining.


Using Spread with Arrays

Copy an Array

const original = [1, 2, 3];

// Shallow copy
const copy = [...original];

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

Spread creates a new array with the same elements.

Combine Arrays

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

const combined = [...arr1, ...arr2];
console.log(combined);  // [1, 2, 3, 4]

Spread expands both arrays into a new one.

Insert Elements

const arr = [1, 2, 5];

// Insert 3 and 4 between 2 and 5
const result = [1, 2, 3, 4, 5];

// Or do it with spread
const start = [1, 2];
const end = [5];
const final = [...start, 3, 4, ...end];
console.log(final);  // [1, 2, 3, 4, 5]

Remove Duplicates

const arr = [1, 2, 2, 3, 3, 3, 4];

// Using Set (removes duplicates)
const unique = [...new Set(arr)];
console.log(unique);  // [1, 2, 3, 4]

Spread converts the Set back to an array.

Reverse an Array

const arr = [1, 2, 3];

// Using spread with reverse
const reversed = [...arr].reverse();
console.log(arr);        // [1, 2, 3] (original unchanged)
console.log(reversed);   // [3, 2, 1] (new reversed array)

Spread creates a copy first, so the original isn't modified.


Using Spread with Objects

Copy an Object

const original = { name: "John", age: 30 };

// Shallow copy
const copy = { ...original };

console.log(copy);              // { name: "John", age: 30 }
console.log(copy === original); // false (different objects)

Spread creates a new object with the same properties.

Merge Objects

const obj1 = { name: "John", age: 30 };
const obj2 = { city: "New York", country: "USA" };

const merged = { ...obj1, ...obj2 };
console.log(merged);
// { name: "John", age: 30, city: "New York", country: "USA" }

Spread expands both objects into one.

Override Properties

const defaults = { theme: "light", language: "en" };
const userSettings = { theme: "dark" };

const settings = { ...defaults, ...userSettings };
console.log(settings);
// { theme: "dark", language: "en" }

Properties from userSettings override defaults because they come later.

Add New Properties

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

const updated = {
  ...user,
  age: 31,
  email: "john@example.com"
};

console.log(updated);
// { name: "John", age: 31, email: "john@example.com" }

Spread the original, then add or override properties.

Exclude Properties

const user = { name: "John", age: 30, password: "secret" };

// Remove password
const { password, ...publicUser } = user;

console.log(publicUser);
// { name: "John", age: 30 }

Rest collects everything except the excluded property.


Practical Use Cases

Use Case 1: Flexible Function Arguments

// Without rest - limited to exact arguments
function oldSum(a, b, c) {
  return a + b + c;
}

oldSum(1, 2, 3);       // 6
oldSum(1, 2, 3, 4);    // 10 (4 ignored)

// With rest - any number of arguments
function newSum(...numbers) {
  return numbers.reduce((sum, n) => sum + n, 0);
}

newSum(1, 2, 3);       // 6
newSum(1, 2, 3, 4);    // 10
newSum(1, 2, 3, 4, 5); // 15

Use Case 2: Cloning Data

const original = { id: 1, name: "Product", price: 99.99 };

// Create a copy to modify
const edited = { ...original, price: 79.99 };

console.log(original); // { id: 1, name: "Product", price: 99.99 }
console.log(edited);   // { id: 1, name: "Product", price: 79.99 }

The original remains unchanged.

Use Case 3: Passing Arguments

function greet(greeting, name, punctuation) {
  return greeting + " " + name + punctuation;
}

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

// Without spread
greet(args[0], args[1], args[2]);  // "Hello Alice!"

// With spread
greet(...args);  // "Hello Alice!"

Spread converts the array to function arguments.

Use Case 4: State Management (React-style)

// Update a single property in state
const state = { user: "John", count: 5, loading: false };

const newState = {
  ...state,
  count: state.count + 1
};

console.log(state);    // { user: "John", count: 5, loading: false }
console.log(newState); // { user: "John", count: 6, loading: false }

Create a new object with one property changed.

Use Case 5: API Response Handling

const apiResponse = {
  status: 200,
  data: { id: 1, name: "John" },
  timestamp: "2026-05-04"
};

// Extract just the data
const { status, timestamp, ...userData } = apiResponse;

console.log(userData);  // { id: 1, name: "John" }

Rest extracts the important data.

Use Case 6: Building Arrays Dynamically

const baseItems = ["apple", "banana"];
const newItems = ["orange"];
const userItems = ["grape"];

const groceryList = [
  "store items:",
  ...baseItems,
  ...newItems,
  ...userItems
];

console.log(groceryList);
// ["store items:", "apple", "banana", "orange", "grape"]

Spread combines multiple arrays cleanly.

Use Case 7: Default Parameters with Objects

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

function makeRequest(url, config = {}) {
  const finalConfig = { ...defaultConfig, ...config };
  return finalConfig;
}

makeRequest("https://api.example.com");
// { timeout: 5000, retries: 3, verbose: false }

makeRequest("https://api.example.com", { timeout: 10000 });
// { timeout: 10000, retries: 3, verbose: false }

Merge user config with defaults.

Use Case 8: Filtering with Rest

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

const [first, ...rest] = numbers;
const even = rest.filter(n => n % 2 === 0);

console.log(even);  // [2, 4]

Extract and process remaining elements.


Spread and Rest Diagrams

Spread: Expanding Elements

Array: [1, 2, 3]
    ↓
Spread: ...arr
    ↓
Expanded: 1, 2, 3
    ↓
Used in: Function calls, array literals, object literals

Rest: Collecting Values

Values: 1, 2, 3, 4, 5
    ↓
Rest: ...rest
    ↓
Collected: [1, 2, 3, 4, 5]
    ↓
Stored in: Array parameter, destructuring

Visual Comparison

SPREAD (Unpacking)
        [1, 2, 3]
              ↓
        1 , 2 , 3
              ↓
    Pass to function / merge

REST (Collecting)
        1 , 2 , 3
              ↓
        [1, 2, 3]
              ↓
    Store in variable

Advanced Examples

Combining Spread and Rest

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

// Destructure with rest
const [first, second, ...remaining] = arr;

// Spread to reconstruct
const reconstructed = [first, second, ...remaining];

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

Deep Object Merging (Single Level)

const user = { name: "John", address: { city: "NY" } };
const update = { address: { zip: "10001" } };

// Shallow merge - overwrites entire address
const result = { ...user, ...update };
console.log(result);
// { name: "John", address: { zip: "10001" } }
// Note: city is lost!

// For deep merge, handle nested objects
const deepResult = {
  ...user,
  address: { ...user.address, ...update.address }
};
console.log(deepResult);
// { name: "John", address: { city: "NY", zip: "10001" } }

Rest with Named Parameters

function createUser(firstName, lastName, ...metadata) {
  return {
    name: firstName + " " + lastName,
    meta: metadata
  };
}

const user = createUser(
  "John",
  "Doe",
  { role: "admin" },
  { permissions: ["read", "write"] }
);

console.log(user);
// {
//   name: "John Doe",
//   meta: [{ role: "admin" }, { permissions: ["read", "write"] }]
// }

Spread in Conditional Logic

const features = ["auth", "api"];
const allFeatures = ["auth", "api", "admin", "logging"];

// Check if all features are included
const hasAllFeatures = features.every(f => allFeatures.includes(f));

// Or, spread and use includes
const isSubset = [...features].every(f => allFeatures.includes(f));

Common Mistakes

Mistake 1: Using Rest in Wrong Position

// WRONG - rest must be last
function test(a, ...rest, b) { }

// RIGHT
function test(a, b, ...rest) { }

Rest collects all remaining values. It must be last.

Mistake 2: Confusing Spread and Rest by Symbol

// WRONG - looks the same but different meaning
const arr = [...[1, 2, 3]];  // Spread - unpacking
const fn = (...args) => {};  // Rest - collecting

// RIGHT - understand the context
// In array/call: spread
// In parameters/destructuring: rest

Mistake 3: Deep Copy with Spread

const original = { user: { name: "John" } };

// WRONG - only shallow copy
const copy = { ...original };
copy.user.name = "Jane";

console.log(original.user.name);  // "Jane" (changed!)

// RIGHT - for nested objects, use deep copy or spread nested objects
const deepCopy = {
  ...original,
  user: { ...original.user }
};

Spread only does shallow copying.

Mistake 4: Spreading Non-Iterables

// WRONG - numbers are not iterable
const num = 123;
const arr = [...num];  // TypeError

// RIGHT - only spread iterables (arrays, strings, objects with Symbol.iterator)
const str = "hello";
const arr = [...str];  // ["h", "e", "l", "l", "o"]

Mistake 5: Rest in Middle of Destructuring

// WRONG - rest must be last
const { a, ...rest, b } = obj;

// RIGHT
const { a, b, ...rest } = obj;

Quick Recap

  • Spread operator (...) in array/object literals or function calls: Expands elements.

    • console.log(...[1,2,3]) → prints 1 2 3
    • Math.max(...[5,6,2]) → returns 6
    • {...obj} → creates a copy
  • Rest operator (...) in function parameters or destructuring: Collects values into an array.

    • function(...args) → collects all arguments
    • const [a, ...rest] = arr → collects remaining elements
    • const { name, ...rest } = obj → collects remaining properties
  • Context determines meaning:

    • In arrays/calls: Spread (unpacking)
    • In parameters/destructuring: Rest (collecting)
  • Spread uses:

    • Copy arrays and objects
    • Merge arrays and objects
    • Pass array elements as function arguments
    • Insert elements into arrays
  • Rest uses:

    • Accept variable number of arguments
    • Destructure arrays and objects
    • Extract specific values and collect the rest
  • Spread is shallow - nested objects/arrays aren't fully copied.

  • Rest must be last - it collects all remaining values.

Master spread and rest. Write cleaner, more flexible code. You'll use them constantly.

Happy coding! 🚀


If you enjoyed this article, check out my other blogs on this profile. 🔗 Connect with me: LinkedIn | GitHub | X (Twitter)