Learning Objectives
- Understand spread vs rest operators
- Use spread with arrays and objects
- Apply rest parameters in functions
- Clone and merge data structures
- Master practical patterns
Spread vs Rest: Same Syntax, Different Purpose
Both use three dots (...), but they work in opposite ways:
- Spread: Expands an array/object into individual elements
- Rest: Collects multiple elements into an array
// Spread - expands array
const arr = [1, 2, 3];
console.log(...arr); // 1 2 3
// Rest - collects into array
function sum(...numbers) {
return numbers.reduce((a, b) => a + b);
}
Spread Operator with Arrays
Copy 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]
Merge Arrays
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
// Old way
const merged = arr1.concat(arr2);
// With spread
const merged = [...arr1, ...arr2];
console.log(merged); // [1, 2, 3, 4, 5, 6]
// Insert in middle
const combined = [...arr1, 99, ...arr2];
console.log(combined); // [1, 2, 3, 99, 4, 5, 6]
Add Elements
const numbers = [2, 3, 4];
// Add to beginning
const withStart = [1, ...numbers];
console.log(withStart); // [1, 2, 3, 4]
// Add to end
const withEnd = [...numbers, 5];
console.log(withEnd); // [2, 3, 4, 5]
// Add to both
const withBoth = [0, ...numbers, 5, 6];
console.log(withBoth); // [0, 2, 3, 4, 5, 6]
Convert to Array
// String to array
const str = 'hello';
const chars = [...str];
console.log(chars); // ['h', 'e', 'l', 'l', 'o']
// NodeList to array
const divs = document.querySelectorAll('div');
const divsArray = [...divs];
// Set to array
const set = new Set([1, 2, 3]);
const arr = [...set];
Math Operations
const numbers = [5, 2, 8, 1, 9];
console.log(Math.max(...numbers)); // 9
console.log(Math.min(...numbers)); // 1
// Without spread (old way)
console.log(Math.max.apply(null, numbers));
Spread Operator with Objects
Copy Objects
const original = { name: 'John', age: 30 };
const copy = { ...original };
copy.age = 31;
console.log(original.age); // 30 - unchanged
console.log(copy.age); // 31
Merge Objects
const defaults = { theme: 'light', language: 'en' };
const userPrefs = { theme: 'dark' };
const settings = { ...defaults, ...userPrefs };
console.log(settings);
// { theme: 'dark', language: 'en' }
// userPrefs overwrites defaults
Add/Update Properties
const user = { name: 'John', age: 30 };
// Add property
const withEmail = { ...user, email: 'john@example.com' };
// Update property
const olderUser = { ...user, age: 31 };
// Add multiple
const fullUser = {
...user,
email: 'john@example.com',
country: 'USA'
};
Conditional Properties
const includeEmail = true;
const user = {
name: 'John',
age: 30,
...(includeEmail && { email: 'john@example.com' })
};
console.log(user);
// { name: 'John', age: 30, email: 'john@example.com' }
Rest Parameters
Variable Number of Arguments
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
Rest with Named Parameters
function greet(greeting, ...names) {
return `${greeting} ${names.join(', ')}!`;
}
console.log(greet('Hello', 'John'));
// "Hello John!"
console.log(greet('Hello', 'John', 'Jane', 'Bob'));
// "Hello John, Jane, Bob!"
Rest Must Be Last
// Valid
function fn(a, b, ...rest) {}
// Invalid
// function fn(...rest, a, b) {} // SyntaxError
// function fn(a, ...rest, b) {} // SyntaxError
Real-World Examples
Immutable Array Operations
const todos = [
{ id: 1, text: 'Learn JS', done: false },
{ id: 2, text: 'Build app', done: false }
];
// Add todo
const addTodo = (todos, newTodo) => [...todos, newTodo];
// Remove todo
const removeTodo = (todos, id) =>
todos.filter(todo => todo.id !== id);
// Update todo
const updateTodo = (todos, id, updates) =>
todos.map(todo =>
todo.id === id ? { ...todo, ...updates } : todo
);
const updated = updateTodo(todos, 1, { done: true });
React State Updates
// Add item to array
setItems([...items, newItem]);
// Update object
setUser({ ...user, name: 'Jane' });
// Update nested object
setUser({
...user,
address: {
...user.address,
city: 'Boston'
}
});
Function Composition
function compose(...fns) {
return (x) => fns.reduceRight((acc, fn) => fn(acc), x);
}
const double = x => x * 2;
const addTen = x => x + 10;
const square = x => x * x;
const compute = compose(square, addTen, double);
console.log(compute(5)); // ((5 * 2) + 10)^2 = 400
API Request Builder
function buildRequest(url, options = {}) {
const defaults = {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
};
return {
...defaults,
...options,
headers: {
...defaults.headers,
...options.headers
}
};
}
const request = buildRequest('/api/users', {
method: 'POST',
headers: { 'Authorization': 'Bearer token' }
});
Logger Function
function log(level, ...messages) {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] [${level}]`, ...messages);
}
log('INFO', 'User logged in');
log('ERROR', 'Failed to fetch', { userId: 123 });
Merge Configurations
const defaultConfig = {
port: 3000,
host: 'localhost',
database: {
host: 'localhost',
port: 5432
}
};
const userConfig = {
port: 8080,
database: {
host: 'db.example.com'
}
};
// Deep merge
const config = {
...defaultConfig,
...userConfig,
database: {
...defaultConfig.database,
...userConfig.database
}
};
Performance Considerations
Shallow Copy Only
Spread creates shallow copies. Nested objects/arrays are still referenced:
const original = { user: { name: 'John' } };
const copy = { ...original };
copy.user.name = 'Jane';
console.log(original.user.name); // 'Jane' - modified!
Performance with Large Arrays
Spread is fast for small to medium arrays, but for very large arrays, consider other methods:
// For large arrays
const merged = arr1.concat(arr2); // Faster
// vs
const merged = [...arr1, ...arr2]; // Slower for huge arrays
Common Patterns
Remove Item from Array
const items = [1, 2, 3, 4, 5];
const index = 2;
const newItems = [
...items.slice(0, index),
...items.slice(index + 1)
];
console.log(newItems); // [1, 2, 4, 5]
Toggle Boolean Property
const user = { name: 'John', active: false };
const toggled = { ...user, active: !user.active };
Flatten Array One Level
const nested = [[1, 2], [3, 4], [5, 6]];
const flat = [].concat(...nested);
console.log(flat); // [1, 2, 3, 4, 5, 6]
// Or use flat()
const flat = nested.flat();
Key Takeaways
- Spread (
...) expands arrays/objects into individual elements - Rest (
...) collects elements into an array - Use spread to copy, merge, and add elements
- Use rest for variable-length function parameters
- Spread creates shallow copies only
- Rest parameters must be last in function signature
- Both enable immutable data patterns
- Essential for modern JavaScript and React development