Spread with Arrays
The spread operator (...) expands an iterable (array, string, Set, Map)
into individual elements wherever a list of values is expected.
const nums = [1, 2, 3];
const more = [4, 5, 6];
// Expand into a new array
const all = [...nums, ...more];
console.log(all); // [1, 2, 3, 4, 5, 6]
// Insert in the middle
const inserted = [0, ...nums, 3.5, ...more];
console.log(inserted); // [0, 1, 2, 3, 3.5, 4, 5, 6]
// Spread a string into characters
const chars = [..."hello"];
console.log(chars); // ["h","e","l","l","o"]
// Spread an iterable (Set, Map)
const unique = [...new Set([1, 2, 2, 3, 3, 4])];
console.log(unique); // [1, 2, 3, 4]
// Spread in Math functions
const prices = [9.99, 14.99, 4.99, 24.99];
console.log(Math.max(...prices)); // 24.99
console.log(Math.min(...prices)); // 4.99
Spread with Objects
const defaults = { theme: "light", lang: "en", timeout: 5000 };
const userPrefs = { theme: "dark", lang: "fr" };
// Merge objects (right side wins on conflicts)
const config = { ...defaults, ...userPrefs };
console.log(config);
// { theme: "dark", lang: "fr", timeout: 5000 }
// Add/override while spreading
const updated = {
...defaults,
theme: "dark", // override
newProp: true // add
};
// Spread enumerates own enumerable properties
// Methods from prototype are NOT spread
class User { greet() {} }
const alice = new User();
alice.name = "Alice";
const plain = { ...alice };
console.log(plain); // { name: "Alice" }
console.log(plain.greet); // undefined (prototype method not included)
// Spread does not copy non-enumerable properties
const obj = Object.defineProperty({}, "hidden", { value: 42, enumerable: false });
console.log({ ...obj }); // {} (non-enumerable not included)
Cloning and Shallow Copy
// Shallow copy of an array
const original = [1, 2, 3, 4, 5];
const copy = [...original];
copy.push(6);
console.log(original); // [1, 2, 3, 4, 5] - unchanged
console.log(copy); // [1, 2, 3, 4, 5, 6]
// Shallow copy of an object
const user = { name: "Alice", age: 28 };
const userCopy = { ...user };
userCopy.age = 30;
console.log(user.age); // 28 - unchanged (primitives copied)
console.log(userCopy.age); // 30
// SHALLOW - nested objects are still shared!
const nested = { info: { score: 100 }, name: "Bob" };
const nestedCopy = { ...nested };
nestedCopy.info.score = 999; // modifies original!
console.log(nested.info.score); // 999 (shared reference!)
// Deep copy - use structuredClone (ES2022)
const deepCopy = structuredClone(nested);
deepCopy.info.score = 0;
console.log(nested.info.score); // 999 - original safe now
// Alternative: JSON round-trip (loses Dates, undefined, functions)
const jsonCopy = JSON.parse(JSON.stringify(nested));
The spread operator copies primitive values (numbers, strings) and copies references to nested objects. If you modify a nested object in the copy, the original is also affected. Use structuredClone() for deep cloning when nested objects must be independent.
Merging Arrays and Objects
// Array merging
const a = [1, 2, 3];
const b = [4, 5, 6];
const merged = [...a, ...b]; // [1,2,3,4,5,6]
const withExtra = [...a, 0, ...b]; // [1,2,3,0,4,5,6]
const reversed = [...b, ...a]; // [4,5,6,1,2,3]
// Remove duplicates while merging
const arr1 = [1, 2, 3];
const arr2 = [2, 3, 4, 5];
const uniqueMerge = [...new Set([...arr1, ...arr2])];
console.log(uniqueMerge); // [1, 2, 3, 4, 5]
// Object merging
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 20, c: 30 }; // b conflicts
const merged2 = { ...obj1, ...obj2 };
console.log(merged2); // { a: 1, b: 20, c: 30 } (obj2.b wins)
// Immutable state update pattern (React/Redux)
const state = { count: 0, user: "Alice", theme: "light" };
const newState = { ...state, count: state.count + 1 };
console.log(state.count); // 0 (original unchanged)
console.log(newState.count); // 1
// Conditionally include properties
const isAdmin = true;
const userObj = {
name: "Alice",
...(isAdmin && { role: "admin", permissions: ["read", "write"] })
};
console.log(userObj); // includes role and permissions
Spread in Function Calls
function add(a, b, c) {
return a + b + c;
}
const nums = [1, 2, 3];
// Old way: apply
console.log(add.apply(null, nums)); // 6
// Spread way (cleaner)
console.log(add(...nums)); // 6
// Mix spread with regular arguments
console.log(add(1, ...nums.slice(1))); // 6
// Passing array to Math functions
const scores = [95, 87, 92, 78, 88];
console.log(Math.max(...scores)); // 95
console.log(Math.min(...scores)); // 78
// Push multiple items into an array
const arr = [1, 2, 3];
const toAdd = [4, 5, 6];
arr.push(...toAdd); // instead of arr.push(4, 5, 6) or concat
console.log(arr); // [1, 2, 3, 4, 5, 6]
// Spread with new (you cannot use apply with new)
const dateArgs = [2024, 0, 15]; // Jan 15, 2024
const date = new Date(...dateArgs);
console.log(date.toDateString()); // "Mon Jan 15 2024"
Rest Parameters
Rest parameters collect remaining function arguments into a real Array. They must be the last parameter.
// Basic rest 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
console.log(sum()); // 0
// First param fixed, rest collected
function log(level, ...messages) {
const prefix = `[${level.toUpperCase()}]`;
messages.forEach(msg => console.log(prefix, msg));
}
log("info", "Server started", "Listening on port 3000");
// [INFO] Server started
// [INFO] Listening on port 3000
// Rest is a real Array - can use all array methods
function joinWords(separator, ...words) {
return words
.map(w => w.trim())
.filter(Boolean)
.join(separator);
}
console.log(joinWords(", ", "Hello", " World ", "", "JS")); // "Hello, World, JS"
// Arrow functions with rest (no arguments object in arrow functions)
const multiply = (...nums) => nums.reduce((acc, n) => acc * n, 1);
console.log(multiply(2, 3, 4)); // 24
Spread vs Rest - At a Glance
| Concept | Spread | Rest |
|---|---|---|
| Symbol | ... | ... |
| Direction | Expands (one to many) | Collects (many to one) |
| Context | Array/object literals, function calls | Function parameters, destructuring |
| Result type | Spreads into surrounding context | Always a real Array |
| Position | Anywhere in the expression | Must be last |
| Works with | Any iterable (array, string, Set, Map) | Function arguments |
// SPREAD: ... in a value position (expands)
const arr = [1, 2, 3];
const copy = [...arr]; // spread in array literal
Math.max(...arr); // spread in function call
const obj = { ...other }; // spread in object literal
// REST: ... in a pattern position (collects)
function fn(...args) {} // rest in function parameters
const [a, ...rest] = arr; // rest in array destructuring
const { x, ...others } = obj; // rest in object destructuring