Back to Blog
JavaScriptSpreadRestES6

JavaScript Spread and Rest Operators

Master the spread and rest operators for arrays, objects, and function parameters.

B
Bootspring Team
Engineering
August 16, 2018
8 min read

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 = 1

Object 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 stored

Performance 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.

Share this article

Help spread the word about Bootspring