Spread Operator (...)
The spread operator (...) expands an iterable (array, string, object) into individual elements. It looks like rest but does the opposite — spread expands, rest collects.
// Spread arrays:
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1,2,3,4,5,6]
const withExtra = [0, ...arr1, ...arr2, 7]; // [0,1,2,3,4,5,6,7]
// Spread creates a SHALLOW copy:
const original = [1, 2, 3];
const copy = [...original];
copy.push(99);
console.log(original); // [1,2,3] — unchanged
// Spread into function arguments:
const numbers = [3, 1, 4, 1, 5, 9, 2];
console.log(Math.max(...numbers)); // 9
console.log(Math.min(...numbers)); // 1
// Spread objects:
const defaults = { theme: "dark", lang: "en", size: 14 };
const custom = { lang: "fr", size: 16 };
const merged = { ...defaults, ...custom };
// { theme: "dark", lang: "fr", size: 16 }
// Later properties override earlier ones
// Shallow copy an object:
const user = { name: "Alice", address: { city: "NYC" } };
const userCopy = { ...user };
userCopy.name = "Bob"; // doesn't affect original
userCopy.address.city = "LA"; // DOES affect original! (shallow copy)
// Deep clone needs structuredClone():
const deepCopy = structuredClone(user);
Rest Parameters
// Rest collects remaining elements into an array
function sum(...numbers) {
return numbers.reduce((acc, n) => acc + n, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
// Mix with regular params — rest must be last!
function logMessage(level, ...messages) {
const prefix = `[${level.toUpperCase()}]`;
console.log(prefix, ...messages);
}
logMessage("info", "Server", "started", "on port 3000");
// 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]
const { name, ...otherProps } = { name: "Alice", age: 30, city: "NYC" };
console.log(name); // "Alice"
console.log(otherProps); // { age: 30, city: "NYC" }
// This is useful for "omitting" a property from an object!
Practical Patterns
// Remove a property from object (non-mutating):
const { password, ...safeUser } = userWithPassword;
// safeUser doesn't have password — safe to log/return
// Merge with override:
function updateUser(user, updates) {
return { ...user, ...updates, updatedAt: new Date() };
}
// Clone and add property:
const withId = { ...newItem, id: generateId() };
// Function that accepts any number of items:
const addToCart = (...items) => {
return items.map(item => ({ ...item, addedAt: Date.now() }));
};
// Spread string:
const chars = [..."hello"]; // ["h","e","l","l","o"]
const unique = [...new Set("mississippi")]; // ["m","i","s","p"]
⚡ Key Takeaways
- Spread (
...) expands iterables — into arrays, function args, or objects - Rest (
...) collects remaining items — in function params or destructuring - Same syntax, opposite meaning — context determines which it is
- Spread creates shallow copies — nested objects are still shared references
- Use
{ ...obj1, ...obj2 }to merge objects — later keys override earlier - Use
const { unwanted, ...rest } = objto omit properties
🎯 Practice Exercises
EXERCISE 1
Write a mergeDefaults(config) function that takes a partial config and returns it merged with sensible defaults for missing properties.
EXERCISE 2
Implement an omit(obj, ...keys) function that returns a new object without the specified keys (use rest and spread destructuring).