The ... syntax serves two purposes: spreading iterables and collecting rest parameters. Here's how to use both effectively.
Spread Operator Basics#
1// Spread arrays
2const arr1 = [1, 2, 3];
3const arr2 = [4, 5, 6];
4
5const combined = [...arr1, ...arr2];
6// [1, 2, 3, 4, 5, 6]
7
8// Add elements
9const withMore = [0, ...arr1, 4, 5];
10// [0, 1, 2, 3, 4, 5]
11
12// Copy array (shallow)
13const copy = [...arr1];
14// [1, 2, 3]
15
16// Spread strings
17const chars = [...'hello'];
18// ['h', 'e', 'l', 'l', 'o']Spread with Objects#
1// Spread objects
2const obj1 = { a: 1, b: 2 };
3const obj2 = { c: 3, d: 4 };
4
5const merged = { ...obj1, ...obj2 };
6// { a: 1, b: 2, c: 3, d: 4 }
7
8// Override properties (last wins)
9const updated = { ...obj1, b: 10 };
10// { a: 1, b: 10 }
11
12// Add new properties
13const extended = { ...obj1, e: 5, f: 6 };
14// { a: 1, b: 2, e: 5, f: 6 }
15
16// Shallow copy
17const copy = { ...obj1 };
18
19// Nested objects (shallow copy caveat)
20const nested = { user: { name: 'John' } };
21const copied = { ...nested };
22copied.user.name = 'Jane';
23console.log(nested.user.name); // 'Jane' - same reference!
24
25// Deep copy nested
26const deepCopied = {
27 ...nested,
28 user: { ...nested.user }
29};Rest Parameters#
1// Collect remaining arguments
2function sum(...numbers) {
3 return numbers.reduce((a, b) => a + b, 0);
4}
5
6sum(1, 2, 3); // 6
7sum(1, 2, 3, 4, 5); // 15
8
9// Rest must be last
10function greet(greeting, ...names) {
11 return names.map(name => `${greeting}, ${name}!`);
12}
13
14greet('Hello', 'John', 'Jane', 'Bob');
15// ['Hello, John!', 'Hello, Jane!', 'Hello, Bob!']
16
17// Rest with required params
18function divide(dividend, divisor, ...rest) {
19 const result = dividend / divisor;
20 console.log('Extra args:', rest);
21 return result;
22}Array Destructuring with Rest#
1// Get first and rest
2const [first, ...rest] = [1, 2, 3, 4, 5];
3console.log(first); // 1
4console.log(rest); // [2, 3, 4, 5]
5
6// Skip elements
7const [a, , b, ...remaining] = [1, 2, 3, 4, 5];
8console.log(a); // 1
9console.log(b); // 3
10console.log(remaining); // [4, 5]
11
12// Head and tail pattern
13function processItems([head, ...tail]) {
14 if (!head) return;
15 console.log('Processing:', head);
16 if (tail.length) processItems(tail);
17}
18
19// Swap values
20let x = 1, y = 2;
21[x, y] = [y, x];
22// x = 2, y = 1Object Destructuring with Rest#
1// Extract specific properties
2const user = { name: 'John', age: 30, email: 'john@example.com', role: 'admin' };
3
4const { name, ...others } = user;
5console.log(name); // 'John'
6console.log(others); // { age: 30, email: 'john@example.com', role: 'admin' }
7
8// Remove properties (create new object without them)
9const { password, ...safeUser } = userWithPassword;
10// safeUser doesn't have password
11
12// Pick specific properties
13const { id, email } = user;
14const picked = { id, email };
15
16// Multiple extractions
17const { name: userName, ...profile } = user;Function Arguments#
1// Apply array as arguments
2function add(a, b, c) {
3 return a + b + c;
4}
5
6const nums = [1, 2, 3];
7add(...nums); // 6
8
9// Math functions
10const values = [5, 2, 8, 1, 9];
11Math.max(...values); // 9
12Math.min(...values); // 1
13
14// Push multiple items
15const arr = [1, 2];
16arr.push(...[3, 4, 5]);
17// [1, 2, 3, 4, 5]
18
19// new with spread
20const dateArgs = [2024, 0, 15];
21const date = new Date(...dateArgs);Copying and Merging#
1// Array copying
2const original = [1, 2, 3];
3const copy = [...original];
4copy.push(4);
5console.log(original); // [1, 2, 3] - unchanged
6
7// Object copying
8const config = { host: 'localhost', port: 3000 };
9const newConfig = { ...config };
10
11// Merge with overrides
12const defaults = { timeout: 5000, retries: 3 };
13const userConfig = { timeout: 10000 };
14const finalConfig = { ...defaults, ...userConfig };
15// { timeout: 10000, retries: 3 }
16
17// Merge arrays (unique values)
18const arr1 = [1, 2, 3];
19const arr2 = [3, 4, 5];
20const unique = [...new Set([...arr1, ...arr2])];
21// [1, 2, 3, 4, 5]Immutable Updates#
1// Add to array
2const todos = [{ id: 1, text: 'Learn JS' }];
3const newTodos = [...todos, { id: 2, text: 'Build app' }];
4
5// Remove from array
6const withoutFirst = todos.slice(1);
7const withoutById = todos.filter(t => t.id !== 1);
8
9// Update item in array
10const updated = todos.map(todo =>
11 todo.id === 1 ? { ...todo, completed: true } : todo
12);
13
14// Update nested object
15const state = {
16 user: { name: 'John', settings: { theme: 'light' } }
17};
18
19const newState = {
20 ...state,
21 user: {
22 ...state.user,
23 settings: {
24 ...state.user.settings,
25 theme: 'dark'
26 }
27 }
28};Conditional Spread#
1// Conditionally include properties
2const includeEmail = true;
3const user = {
4 name: 'John',
5 ...(includeEmail && { email: 'john@example.com' })
6};
7
8// With ternary
9const config = {
10 host: 'localhost',
11 ...(isProd ? { ssl: true, port: 443 } : { port: 3000 })
12};
13
14// Conditional array elements
15const features = [
16 'basic',
17 ...(isPremium ? ['advanced', 'priority'] : []),
18 'support'
19];
20
21// Filter falsy then spread
22const items = [
23 condition1 && 'item1',
24 condition2 && 'item2',
25 'item3'
26].filter(Boolean);Common Patterns#
1// Function with options object
2function createUser({ name, email, ...options }) {
3 const user = { name, email };
4
5 if (options.role) user.role = options.role;
6 if (options.department) user.department = options.department;
7
8 return user;
9}
10
11// Higher-order function
12function withLogging(fn) {
13 return function(...args) {
14 console.log('Arguments:', args);
15 const result = fn(...args);
16 console.log('Result:', result);
17 return result;
18 };
19}
20
21// Compose functions
22const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
23
24const process = compose(
25 x => x * 2,
26 x => x + 1,
27 x => x * 3
28);
29
30// Pipe (left to right)
31const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);Converting Iterables#
1// NodeList to Array
2const divs = document.querySelectorAll('div');
3const divArray = [...divs];
4
5// Set to Array
6const set = new Set([1, 2, 3]);
7const arr = [...set];
8
9// Map to Array
10const map = new Map([['a', 1], ['b', 2]]);
11const entries = [...map]; // [['a', 1], ['b', 2]]
12const keys = [...map.keys()]; // ['a', 'b']
13const values = [...map.values()]; // [1, 2]
14
15// Generator to Array
16function* range(start, end) {
17 for (let i = start; i <= end; i++) yield i;
18}
19const numbers = [...range(1, 5)]; // [1, 2, 3, 4, 5]
20
21// Arguments to Array (in non-arrow functions)
22function example() {
23 const args = [...arguments];
24 return args.map(x => x * 2);
25}Edge Cases#
1// Null/undefined spread (objects only)
2const obj = { ...null }; // {} - no error
3const obj2 = { ...undefined }; // {} - no error
4
5// But not in arrays
6// const arr = [...null]; // TypeError!
7
8// Spread order matters
9const a = { x: 1, y: 2 };
10const b = { y: 3, z: 4 };
11
12const ab = { ...a, ...b }; // { x: 1, y: 3, z: 4 }
13const ba = { ...b, ...a }; // { x: 1, y: 2, z: 4 }
14
15// Getters are called
16const withGetter = {
17 get value() {
18 console.log('Getter called');
19 return 42;
20 }
21};
22
23const spread = { ...withGetter };
24// 'Getter called' - getter executed, result storedPerformance Considerations#
1// Creating new arrays/objects has overhead
2// Don't spread in tight loops unnecessarily
3
4// ❌ Inefficient
5let result = [];
6for (const item of items) {
7 result = [...result, process(item)];
8}
9
10// ✓ Better
11const result = items.map(process);
12
13// ✓ Or push to existing array
14const result = [];
15for (const item of items) {
16 result.push(process(item));
17}
18
19// Large arrays: concat may be faster than spread
20const big1 = Array(10000).fill(1);
21const big2 = Array(10000).fill(2);
22
23// Spread creates intermediate array
24const combined1 = [...big1, ...big2];
25
26// concat might be faster for large arrays
27const combined2 = big1.concat(big2);Best Practices#
Spread Arrays:
✓ Copy arrays immutably
✓ Combine arrays
✓ Convert iterables
✓ Spread into function calls
Spread Objects:
✓ Shallow copy objects
✓ Merge configurations
✓ Add/override properties
✓ Immutable updates
Rest Parameters:
✓ Variable argument functions
✓ Destructuring remainder
✓ Forwarding arguments
✓ Always last parameter
Avoid:
✗ Spread in tight loops
✗ Assuming deep copy
✗ Spreading non-iterables
✗ Overcomplicating simple operations
Conclusion#
The spread (...) and rest operators are versatile tools for working with arrays, objects, and function parameters. Use spread for copying, merging, and converting iterables. Use rest for collecting function arguments and destructuring remainders. Remember that spread creates shallow copies - nested objects still share references. These operators enable clean, functional-style JavaScript with immutable data patterns.