Spread vs Rest Operators in JavaScript

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])→ prints1 2 3Math.max(...[5,6,2])→ returns6{...obj}→ creates a copy
Rest operator (
...) in function parameters or destructuring: Collects values into an array.function(...args)→ collects all argumentsconst [a, ...rest] = arr→ collects remaining elementsconst { 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)




