Spread and Rest Operators
The Three Dots That Do Everything
Explanation
What are Spread and Rest?
The ... syntax serves two purposes: spreading arrays/objects apart, and gathering items together. Same syntax, opposite effects depending on context.
Key Concepts
- Spread: Expands an iterable into individual elements
- Rest: Collects multiple elements into an array
- Context Determines Behavior: Left side = rest, right side = spread
Quick Reference
// Spread - expands
const arr = [...other]; // Copy array
const obj = {...other}; // Copy object
// Rest - collects
const [first, ...rest] = arr; // Rest in destructuring
function fn(...args) {} // Rest in parameters
Demonstration
Example 1: Spread with 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]
// Concatenating arrays
const arr1 = [1, 2];
const arr2 = [3, 4];
const combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4]
// Adding elements
const withStart = [0, ...original]; // [0, 1, 2, 3]
const withEnd = [...original, 4]; // [1, 2, 3, 4]
const withMiddle = [1, ...arr2, 5]; // [1, 3, 4, 5]
// Spreading strings
const chars = [..."hello"];
console.log(chars); // ['h', 'e', 'l', 'l', 'o']
// Converting iterables to arrays
const set = new Set([1, 2, 3]);
const fromSet = [...set]; // [1, 2, 3]
const nodeList = document.querySelectorAll('div');
const divArray = [...nodeList];
// Function arguments
const numbers = [5, 2, 8, 1, 9];
console.log(Math.max(...numbers)); // 9
console.log(Math.min(...numbers)); // 1
Example 2: Spread with Objects
// Copying objects (shallow)
const user = { name: 'Arthur', age: 30 };
const userCopy = { ...user };
userCopy.age = 31;
console.log(user.age); // 30 - unchanged
console.log(userCopy.age); // 31
// Merging objects
const defaults = { theme: 'light', lang: 'en' };
const settings = { theme: 'dark' };
const merged = { ...defaults, ...settings };
console.log(merged); // { theme: 'dark', lang: 'en' }
// Order matters - later wins
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
console.log({ ...obj1, ...obj2 }); // { a: 1, b: 3, c: 4 }
console.log({ ...obj2, ...obj1 }); // { a: 1, b: 2, c: 4 }
// Adding/overriding properties
const withNewProp = { ...user, role: 'admin' };
const withOverride = { ...user, name: 'Art' };
// Conditional spreading
const isAdmin = true;
const adminUser = {
...user,
...(isAdmin && { role: 'admin', permissions: ['all'] })
};
// Removing properties (with destructuring)
const { age, ...userWithoutAge } = user;
console.log(userWithoutAge); // { name: 'Arthur' }
Example 3: Rest Parameters
// Rest in function parameters
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
// Rest with regular parameters
function log(message, ...args) {
console.log(message, args);
}
log('Users:', 'Alice', 'Bob', 'Charlie');
// Users: ['Alice', 'Bob', 'Charlie']
// Rest must be last
// function bad(a, ...rest, b) {} // SyntaxError!
// Rest in destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5]
// Rest with objects
const { name, ...otherProps } = { name: 'Arthur', age: 30, role: 'admin' };
console.log(name); // 'Arthur'
console.log(otherProps); // { age: 30, role: 'admin' }
Example 4: Practical Patterns
// Immutable state updates (React pattern)
const state = {
user: { name: 'Arthur', settings: { theme: 'dark' } },
posts: [{ id: 1, title: 'Hello' }]
};
// Update nested property immutably
const newState = {
...state,
user: {
...state.user,
settings: {
...state.user.settings,
theme: 'light'
}
}
};
// Add item to array immutably
const withNewPost = {
...state,
posts: [...state.posts, { id: 2, title: 'World' }]
};
// Remove item from array immutably
const withoutFirst = {
...state,
posts: state.posts.filter(p => p.id !== 1)
};
// Update item in array immutably
const withUpdatedPost = {
...state,
posts: state.posts.map(p =>
p.id === 1 ? { ...p, title: 'Updated' } : p
)
};
// Function composition with spread
function pipe(...fns) {
return (value) => fns.reduce((acc, fn) => fn(acc), value);
}
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;
const transform = pipe(addOne, double, square);
console.log(transform(2)); // ((2 + 1) * 2)² = 36
Example 5: Common Use Cases
// Clone and extend
const createUser = (base, overrides) => ({
id: Date.now(),
createdAt: new Date(),
...base,
...overrides
});
const user = createUser(
{ name: 'Arthur', role: 'user' },
{ role: 'admin' }
);
// Array deduplication
const withDupes = [1, 2, 2, 3, 3, 3];
const unique = [...new Set(withDupes)];
console.log(unique); // [1, 2, 3]
// Conditional array elements
const permissions = [
'read',
'write',
...(isAdmin ? ['delete', 'admin'] : [])
];
// Props forwarding in React
function Button({ children, ...props }) {
return <button {...props}>{children}</button>;
}
// <Button className="primary" onClick={handleClick}>Click</Button>
// API response transformation
function transformUser({ id, attributes, relationships }) {
return {
id,
...attributes,
friends: relationships?.friends?.data || []
};
}
// Merge configurations
function mergeConfig(...configs) {
return configs.reduce((merged, config) => ({
...merged,
...config,
// Deep merge for nested objects
headers: { ...merged.headers, ...config.headers }
}), {});
}
const config = mergeConfig(
{ timeout: 1000, headers: { 'Accept': 'application/json' } },
{ timeout: 2000, headers: { 'Authorization': 'Bearer token' } }
);
Key Takeaways:
- Spread expands, rest collects
- Both use
...syntax - Essential for immutable updates
- Shallow copy only (not deep)
- Order matters when merging
Imitation
Challenge 1: Implement Merge Deep
Task: Create a function that deeply merges objects.
Solution
function mergeDeep(target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) {
target[key] = {};
}
mergeDeep(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
return mergeDeep(target, ...sources);
}
function isObject(item) {
return item && typeof item === 'object' && !Array.isArray(item);
}
// Immutable version
function mergeDeepImmutable(...objects) {
return objects.reduce((result, obj) => {
Object.keys(obj).forEach(key => {
if (isObject(result[key]) && isObject(obj[key])) {
result[key] = mergeDeepImmutable(result[key], obj[key]);
} else {
result[key] = obj[key];
}
});
return result;
}, {});
}
Challenge 2: Create Variadic Function Wrapper
Task: Build a wrapper that logs all arguments and return value.
Solution
function withLogging(fn, name = fn.name) {
return function(...args) {
console.log(`${name} called with:`, args);
const result = fn.apply(this, args);
if (result instanceof Promise) {
return result.then(value => {
console.log(`${name} resolved:`, value);
return value;
}).catch(error => {
console.log(`${name} rejected:`, error);
throw error;
});
}
console.log(`${name} returned:`, result);
return result;
};
}
// Usage
const add = (a, b) => a + b;
const loggedAdd = withLogging(add);
loggedAdd(2, 3);
// add called with: [2, 3]
// add returned: 5
// With async
const fetchUser = async (id) => ({ id, name: 'User' });
const loggedFetch = withLogging(fetchUser, 'fetchUser');
await loggedFetch(1);
// fetchUser called with: [1]
// fetchUser resolved: { id: 1, name: 'User' }
Practice
Exercise 1: Array Utilities
Difficulty: Intermediate
Create utility functions using spread/rest:
first(arr, n)- Get first n itemslast(arr, n)- Get last n itemswithout(arr, ...values)- Remove values
Exercise 2: Object Utilities
Difficulty: Advanced
Build object utility functions:
pick(obj, ...keys)- Select keysomit(obj, ...keys)- Exclude keysdefaults(obj, ...sources)- Apply defaults
Summary
What you learned:
- Spread expands arrays and objects
- Rest collects into arrays
- Essential for immutable patterns
- Shallow copying behavior
- Practical use cases
Next Steps:
- Read: JavaScript Classes
- Practice: Refactor mutations to immutable
- Build: State management without mutation
