Spread and Rest Operators
Spread and Rest Operators (…)
The three dots ... operator in JavaScript serves two purposes: spread and rest. Despite looking identical, they do opposite things depending on where they’re used.
The Spread Operator
The spread operator expands an array or object into individual elements.
Spreading Arrays
Copying arrays:
const original = [1, 2, 3];
const copy = [...original];
copy.push(4);
console.log(original); // [1, 2, 3] - unchanged
console.log(copy); // [1, 2, 3, 4]
Combining arrays:
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]
Adding elements:
const numbers = [2, 3, 4];
const withMore = [1, ...numbers, 5, 6];
console.log(withMore); // [1, 2, 3, 4, 5, 6]
Passing array elements as function arguments:
const numbers = [1, 5, 3, 9, 2];
console.log(Math.max(...numbers)); // 9
// Same as: Math.max(1, 5, 3, 9, 2)
Spreading Objects
Copying objects:
const person = { name: "Alice", age: 25 };
const personCopy = { ...person };
personCopy.age = 26;
console.log(person.age); // 25 - unchanged
console.log(personCopy.age); // 26
Combining objects:
const defaults = { theme: "light", language: "en" };
const userPrefs = { language: "es", fontSize: 14 };
const settings = { ...defaults, ...userPrefs };
// { theme: "light", language: "es", fontSize: 14 }
// Later values override earlier ones
Adding/updating properties:
const user = { name: "Bob", age: 30 };
const updatedUser = { ...user, age: 31, city: "NYC" };
console.log(updatedUser);
// { name: "Bob", age: 31, city: "NYC" }
The Rest Operator
The rest operator collects multiple elements into an array. It’s used in function parameters and destructuring.
Rest Parameters in Functions
Collect remaining arguments into an array:
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
Combining with regular parameters:
function greet(greeting, ...names) {
return greeting + ", " + names.join(" and ");
}
console.log(greet("Hello", "Alice")); // "Hello, Alice"
console.log(greet("Hello", "Alice", "Bob")); // "Hello, Alice and Bob"
console.log(greet("Hi", "Alice", "Bob", "Charlie"));
// "Hi, Alice and Bob and Charlie"
Important: Rest parameter must be the last parameter:
// ✓ Correct
function example(first, ...rest) { }
// ✗ Wrong - rest must be last
function wrong(...rest, last) { } // SyntaxError!
Rest in Destructuring
Array destructuring:
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5]
Object destructuring:
const person = {
name: "Alice",
age: 25,
city: "NYC",
country: "USA"
};
const { name, ...otherInfo } = person;
console.log(name); // "Alice"
console.log(otherInfo); // { age: 25, city: "NYC", country: "USA" }
Practical Examples
Example 1: Immutable Array Operations
const todos = [
{ id: 1, text: "Learn JS", done: false },
{ id: 2, text: "Build app", done: false }
];
// Add item (without mutating original)
const addTodo = (todos, newTodo) => [...todos, newTodo];
// Remove item
const removeTodo = (todos, id) => todos.filter(todo => todo.id !== id);
// Update item
const updateTodo = (todos, id, updates) =>
todos.map(todo =>
todo.id === id ? { ...todo, ...updates } : todo
);
const newTodos = addTodo(todos, { id: 3, text: "Deploy", done: false });
console.log(newTodos.length); // 3
console.log(todos.length); // 2 - original unchanged
Example 2: Function with Optional Config
function createUser(name, email, options = {}) {
const defaults = {
role: "user",
active: true,
notifications: true
};
return {
name,
email,
...defaults,
...options // Options override defaults
};
}
const user1 = createUser("Alice", "alice@example.com");
// { name: "Alice", email: "alice@...", role: "user", active: true, ... }
const user2 = createUser("Bob", "bob@example.com", { role: "admin", active: false });
// { name: "Bob", email: "bob@...", role: "admin", active: false, ... }
Example 3: Logger with Variable Arguments
function log(level, ...messages) {
const timestamp = new Date().toISOString();
const formatted = messages.join(" ");
console.log(`[${timestamp}] ${level}: ${formatted}`);
}
log("INFO", "User", "logged", "in");
// [2024-01-15T10:30:00.000Z] INFO: User logged in
log("ERROR", "Failed", "to", "save", "data");
// [2024-01-15T10:30:01.000Z] ERROR: Failed to save data
Example 4: React State Updates
// Common pattern in React
const [user, setUser] = useState({ name: "Alice", age: 25 });
// Update one property immutably
setUser({ ...user, age: 26 });
// Or with prev state
setUser(prev => ({ ...prev, age: prev.age + 1 }));
Shallow vs Deep Copy
Warning: Spread creates shallow copies only:
const original = {
name: "Alice",
address: { city: "NYC", country: "USA" }
};
const copy = { ...original };
copy.address.city = "LA"; // This changes original too!
console.log(original.address.city); // "LA" - uh oh!
For nested objects, you need deep cloning:
// Simple deep clone (loses functions, dates, etc.)
const deepCopy = JSON.parse(JSON.stringify(original));
// Or use a library like lodash
const deepCopy = _.cloneDeep(original);
When to Use Spread vs Rest
| Use Case | Operator | Example |
|---|---|---|
| Copy array | Spread | [...arr] |
| Copy object | Spread | {...obj} |
| Merge arrays | Spread | [...arr1, ...arr2] |
| Merge objects | Spread | {...obj1, ...obj2} |
| Function args | Spread | fn(...args) |
| Collect params | Rest | function(...args) {} |
| Destructure array | Rest | [first, ...rest] = arr |
| Destructure object | Rest | {prop, ...rest} = obj |
Combining Spread and Rest
function logUserAction(action, user, ...details) {
const logEntry = {
timestamp: Date.now(),
action,
user: { ...user }, // Copy user to avoid mutations
details: [...details] // Collect remaining args
};
console.log(logEntry);
}
const user = { id: 1, name: "Alice" };
logUserAction("login", user, "192.168.1.1", "Chrome");
Common Patterns
1. Remove property from object:
const user = { id: 1, name: "Alice", password: "secret123" };
const { password, ...safeUser } = user;
console.log(safeUser); // { id: 1, name: "Alice" }
2. Conditional properties:
const createUser = (name, isAdmin) => ({
name,
createdAt: new Date(),
...(isAdmin && { role: "admin", permissions: ["read", "write"] })
});
console.log(createUser("Alice", true));
// { name: "Alice", createdAt: ..., role: "admin", permissions: [...] }
console.log(createUser("Bob", false));
// { name: "Bob", createdAt: ... }
3. Function composition:
const compose = (...fns) => (value) =>
fns.reduceRight((acc, fn) => fn(acc), value);
const double = x => x * 2;
const addOne = x => x + 1;
const square = x => x * x;
const compute = compose(square, addOne, double);
console.log(compute(3)); // ((3 * 2) + 1)² = 49
Browser Support
Spread and rest are ES6 features, supported in:
- All modern browsers
- Node.js 6+
- Can be transpiled with Babel for older environments
Next Article: Destructuring