What are spread and rest operators?

👨‍💻 Frontend Developer 🟡 Often Asked 🎚️ Medium
#JavaScript #JS Basics #Objects #Arrays

Brief Answer

Spread and rest operators are ES6 syntax using three dots (...). Spread “expands” array elements or object properties, while rest “collects” multiple elements into an array or object. Spread is used when calling functions and creating new data structures, rest is used in function parameters and destructuring.

Key differences:

  • Spread — expands ([...array], {...object})
  • Rest — collects (function(...args), [first, ...rest])
  • Context — spread on the right side, rest on the left
  • Application — copying, merging, passing parameters
  • Immutability — creating new structures without changing originals

What are spread and rest operators

Spread and rest operators are powerful ES6 tools that use the same syntax (...) but perform opposite functions. They simplify working with arrays, objects, and functions, making code more readable and functional.

Main difference

// Spread — expands elements
const numbers = [1, 2, 3];
const newNumbers = [...numbers, 4, 5]; // [1, 2, 3, 4, 5]
 
// Rest — collects elements
function sum(...args) {
  return args.reduce((total, num) => total + num, 0);
}
 
console.log(sum(1, 2, 3, 4)); // 10

Spread Operator (…)

1. Spread with arrays

Copying arrays

const original = [1, 2, 3];
const copy = [...original];
 
console.log(copy); // [1, 2, 3]
console.log(original === copy); // false — different objects
 
// Traditional way
const oldCopy = original.slice();
// or
const oldCopy2 = Array.from(original);

Merging arrays

const fruits = ['apple', 'banana'];
const vegetables = ['carrot', 'cabbage'];
const food = [...fruits, ...vegetables];
 
console.log(food); // ['apple', 'banana', 'carrot', 'cabbage']
 
// Adding elements
const numbers = [2, 3, 4];
const extended = [1, ...numbers, 5, 6];
console.log(extended); // [1, 2, 3, 4, 5, 6]
 
// Inserting in the middle
const start = [1, 2];
const middle = [3, 4];
const end = [5, 6];
const combined = [...start, ...middle, ...end];
console.log(combined); // [1, 2, 3, 4, 5, 6]

Converting string to array

const word = 'hello';
const letters = [...word];
console.log(letters); // ['h', 'e', 'l', 'l', 'o']
 
// Working with emojis
const emoji = '👨‍👩‍👧‍👦';
const emojiArray = [...emoji];
console.log(emojiArray); // Correctly splits compound emojis

2. Spread with objects

Copying objects

const user = {
  name: 'John',
  age: 25,
  city: 'New York'
};
 
const userCopy = {...user};
console.log(userCopy); // {name: 'John', age: 25, city: 'New York'}
console.log(user === userCopy); // false
 
// Shallow copying
const userWithAddress = {
  name: 'Mary',
  address: {street: 'Broadway', house: 10}
};
 
const copy = {...userWithAddress};
copy.address.house = 20; // Changes the original object!
console.log(userWithAddress.address.house); // 20

Merging objects

const basicInfo = {name: 'Peter', age: 30};
const contactInfo = {email: 'peter@example.com', phone: '+1-123-456-78-90'};
const fullInfo = {...basicInfo, ...contactInfo};
 
console.log(fullInfo);
// {name: 'Peter', age: 30, email: 'peter@example.com', phone: '+1-123-456-78-90'}
 
// Overwriting properties
const defaults = {theme: 'light', language: 'en', notifications: true};
const userSettings = {theme: 'dark', notifications: false};
const settings = {...defaults, ...userSettings};
 
console.log(settings);
// {theme: 'dark', language: 'en', notifications: false}

Adding new properties

const user = {name: 'Anna', age: 28};
const userWithId = {id: 1, ...user};
const userWithStatus = {...user, isActive: true, lastLogin: new Date()};
 
console.log(userWithId); // {id: 1, name: 'Anna', age: 28}
console.log(userWithStatus); // {name: 'Anna', age: 28, isActive: true, lastLogin: ...}

3. Spread in function calls

// Passing array elements as separate arguments
function multiply(a, b, c) {
  return a * b * c;
}
 
const numbers = [2, 3, 4];
console.log(multiply(...numbers)); // 24
 
// Finding maximum value
const scores = [85, 92, 78, 96, 88];
const maxScore = Math.max(...scores);
console.log(maxScore); // 96
 
// Adding elements to array
const existingItems = ['item1', 'item2'];
const newItems = ['item3', 'item4'];
existingItems.push(...newItems);
console.log(existingItems); // ['item1', 'item2', 'item3', 'item4']

Rest Operator (…)

1. Rest in function parameters

Functions with variable number of arguments

// Sum of any number of numbers
function sum(...numbers) {
  return numbers.reduce((total, num) => total + num, 0);
}
 
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
console.log(sum()); // 0
 
// Function with required and optional parameters
function greet(greeting, ...names) {
  return `${greeting}, ${names.join(' and ')}!`;
}
 
console.log(greet('Hello', 'John')); // 'Hello, John!'
console.log(greet('Welcome', 'Mary', 'Peter')); // 'Welcome, Mary and Peter!'

Processing arguments

// Logging with different levels
function log(level, message, ...details) {
  console.log(`[${level.toUpperCase()}] ${message}`);
  if (details.length > 0) {
    console.log('Details:', details);
  }
}
 
log('info', 'User logged in');
log('error', 'Connection error', 'timeout', 'server unreachable');
 
// Creating function wrappers
function withLogging(fn) {
  return function(...args) {
    console.log('Function called with arguments:', args);
    const result = fn(...args);
    console.log('Result:', result);
    return result;
  };
}
 
const loggedSum = withLogging(sum);
console.log(loggedSum(1, 2, 3)); // Logs call and result

2. Rest in array destructuring

// Extracting first element and the rest
const numbers = [1, 2, 3, 4, 5];
const [first, ...rest] = numbers;
 
console.log(first); // 1
console.log(rest); // [2, 3, 4, 5]
 
// Extracting multiple elements
const colors = ['red', 'green', 'blue', 'yellow', 'purple'];
const [primary, secondary, ...others] = colors;
 
console.log(primary); // 'red'
console.log(secondary); // 'green'
console.log(others); // ['blue', 'yellow', 'purple']
 
// Getting last elements
function getLastElements(arr, count) {
  const [...allElements] = arr;
  return allElements.slice(-count);
}
 
console.log(getLastElements([1, 2, 3, 4, 5], 2)); // [4, 5]

3. Rest in object destructuring

// Extracting specific properties
const user = {
  id: 1,
  name: 'Dmitry',
  email: 'dmitry@example.com',
  age: 35,
  city: 'San Francisco',
  phone: '+1-987-654-32-10'
};
 
const {id, name, ...otherInfo} = user;
console.log(id); // 1
console.log(name); // 'Dmitry'
console.log(otherInfo); // {email: '...', age: 35, city: '...', phone: '...'}
 
// Excluding sensitive data
const userWithPassword = {
  id: 2,
  username: 'maria_user',
  password: 'secret123',
  email: 'maria@example.com',
  role: 'admin'
};
 
const {password, ...safeUserData} = userWithPassword;
console.log(safeUserData); // Object without password

Practical Examples

1. Working with state in React

// Updating state while preserving previous values
function updateUserProfile(currentUser, updates) {
  return {
    ...currentUser,
    ...updates,
    updatedAt: new Date()
  };
}
 
const user = {id: 1, name: 'John', email: 'john@example.com'};
const updatedUser = updateUserProfile(user, {name: 'John Smith'});
console.log(updatedUser);
// {id: 1, name: 'John Smith', email: 'john@example.com', updatedAt: ...}
 
// Adding item to state array
function addTodo(todos, newTodo) {
  return [...todos, {id: Date.now(), ...newTodo}];
}
 
const todos = [{id: 1, text: 'Buy milk', done: false}];
const newTodos = addTodo(todos, {text: 'Walk the dog', done: false});
console.log(newTodos); // New array with added task

2. Working with APIs

// Processing API response
function processApiResponse(response) {
  const {data, meta, ...otherFields} = response;
  
  return {
    items: data.items || [],
    pagination: meta.pagination || {},
    additionalInfo: otherFields
  };
}
 
// Preparing data for sending
function prepareUserData(formData, additionalFields = {}) {
  const {confirmPassword, ...userData} = formData;
  
  return {
    ...userData,
    ...additionalFields,
    createdAt: new Date().toISOString()
  };
}

3. Utility functions

// Function for merging arrays without duplicates
function uniqueConcat(...arrays) {
  const combined = [].concat(...arrays);
  return [...new Set(combined)];
}
 
const arr1 = [1, 2, 3];
const arr2 = [3, 4, 5];
const arr3 = [5, 6, 7];
console.log(uniqueConcat(arr1, arr2, arr3)); // [1, 2, 3, 4, 5, 6, 7]
 
// Function for deep merging objects
function deepMerge(target, ...sources) {
  if (!sources.length) return target;
  const source = sources.shift();
 
  for (const key in source) {
    if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
      if (!target[key]) target[key] = {};
      deepMerge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
 
  return deepMerge(target, ...sources);
}

4. Functional programming

// Function composition
function compose(...functions) {
  return function(value) {
    return functions.reduceRight((acc, fn) => fn(acc), value);
  };
}
 
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;
 
const composedFunction = compose(square, double, addOne);
console.log(composedFunction(3)); // ((3 + 1) * 2)² = 64
 
// Currying with rest parameters
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn(...args);
    }
    return function(...nextArgs) {
      return curried(...args, ...nextArgs);
    };
  };
}
 
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6

Comparison Table

AspectSpreadRest
Syntax...expression...parameter
PositionRight side of expressionLeft side, parameters
FunctionExpands elementsCollects elements
Arrays[...arr1, ...arr2][first, ...rest]
Objects{...obj1, ...obj2}{prop, ...others}
Functionsfn(...args)function(...params)
ResultNew data structureArray or object
ApplicationCopying, mergingCollecting parameters

Advanced Techniques

1. Conditional spread

// Conditional property addition
function createUser(name, email, isAdmin = false) {
  return {
    name,
    email,
    createdAt: new Date(),
    ...(isAdmin && {role: 'admin', permissions: ['read', 'write', 'delete']})
  };
}
 
console.log(createUser('John', 'john@example.com')); // Without role
console.log(createUser('Mary', 'mary@example.com', true)); // With admin role
 
// Conditional array merging
function getMenuItems(isAuthenticated, isAdmin) {
  const baseItems = ['Home', 'About', 'Contact'];
  const userItems = ['Profile', 'Settings'];
  const adminItems = ['Admin Panel', 'Users'];
  
  return [
    ...baseItems,
    ...(isAuthenticated ? userItems : []),
    ...(isAdmin ? adminItems : [])
  ];
}

2. Spread with computed properties

// Dynamic object creation
function createDynamicObject(key, value, additionalProps = {}) {
  return {
    id: Math.random(),
    timestamp: Date.now(),
    [key]: value,
    ...additionalProps
  };
}
 
const obj1 = createDynamicObject('username', 'john_doe', {email: 'john@example.com'});
const obj2 = createDynamicObject('productName', 'Laptop', {price: 999, category: 'Electronics'});

3. Spread for immutable operations

// Immutable nested object updates
function updateNestedProperty(obj, path, value) {
  const [head, ...tail] = path;
  
  if (tail.length === 0) {
    return {...obj, [head]: value};
  }
  
  return {
    ...obj,
    [head]: updateNestedProperty(obj[head] || {}, tail, value)
  };
}
 
const state = {
  user: {
    profile: {
      settings: {
        theme: 'light'
      }
    }
  }
};
 
const newState = updateNestedProperty(state, ['user', 'profile', 'settings', 'theme'], 'dark');
console.log(newState.user.profile.settings.theme); // 'dark'
console.log(state.user.profile.settings.theme); // 'light' — original unchanged

Best Practices

1. Performance

// ❌ Avoid spread in loops for large arrays
let result = [];
for (let i = 0; i < 10000; i++) {
  result = [...result, i]; // Creates new array each time
}
 
// ✅ Use push for&nbsp;accumulation
let result = [];
for (let i = 0; i < 10000; i++) {
  result.push(i);
}
 
// ✅ Or use spread for&nbsp;final merging
const chunks = [];
for (let i = 0; i < 100; i++) {
  chunks.push(Array.from({length: 100}, (_, j) => i * 100 + j));
}
const result = [].concat(...chunks);

2. Code readability

// ❌ Too many spread operations
const result = {...obj1, ...obj2, ...obj3, ...obj4, ...obj5};
 
// ✅ Group logically related operations
const baseConfig = {...defaultConfig, ...userConfig};
const finalConfig = {...baseConfig, ...environmentConfig};
 
// ❌ Unclear rest usage
function processData({a, b, c, d, e, f, ...rest}) {
  // Too many parameters
}
 
// ✅ Group related parameters
function processData({userInfo, settings, ...metadata}) {
  const {name, email} = userInfo;
  const {theme, language} = settings;
  // More understandable structure
}

3. Type safety

// ❌ Unsafe rest usage
function unsafeFunction(...args) {
  return args[0].toUpperCase(); // Error if args[0] is&nbsp;not a&nbsp;string
}
 
// ✅ Parameter validation
function safeFunction(...args) {
  if (args.length === 0) {
    throw new Error('At least one argument is&nbsp;required');
  }
  
  const [first] = args;
  if (typeof first !== 'string') {
    throw new Error('First argument must be&nbsp;a&nbsp;string');
  }
  
  return first.toUpperCase();
}
 
// ✅ Using default values
function createUser({name, email, ...options} = {}) {
  if (!name || !email) {
    throw new Error('Name and&nbsp;email are required');
  }
  
  return {
    name,
    email,
    isActive: true,
    createdAt: new Date(),
    ...options
  };
}

Modern Features

1. Spread with optional chaining

// Safe spreading of&nbsp;potentially undefined values
const user = {
  name: 'John',
  preferences: null
};
 
const settings = {
  theme: 'light',
  ...user.preferences, // Error!
};
 
// ✅ Safe version
const safeSettings = {
  theme: 'light',
  ...(user.preferences ?? {})
};
 
// ✅ With optional chaining
const config = {
  ...defaultConfig,
  ...user?.preferences,
  ...user?.settings?.ui
};

2. Spread with private fields

// Creating public API from object with private data
function createPublicUser(userData) {
  const {password, internalId, ...publicData} = userData;
  
  return {
    ...publicData,
    id: internalId,
    hasPassword: Boolean(password)
  };
}

3. Spread in TypeScript

// Typed spread
interface User {
  id: number;
  name: string;
  email: string;
}
 
interface UserUpdate {
  name?: string;
  email?: string;
}
 
function updateUser(user: User, updates: UserUpdate): User {
  return {
    ...user,
    ...updates
  };
}
 
// Rest with typing
function logMessages(level: string, ...messages: string[]): void {
  console.log(`[${level}]`, ...messages);
}

Practice Tasks

Task 1: Merging arrays

Condition: Create a function that takes any number of arrays and returns a new array with all unique elements.

function mergeUnique(...arrays) {
  // Your code here
}
 
console.log(mergeUnique([1, 2], [2, 3], [3, 4])); // [1, 2, 3, 4]
console.log(mergeUnique(['a', 'b'], ['b', 'c'], ['c', 'd'])); // ['a', 'b', 'c', 'd']
Solution
function mergeUnique(...arrays) {
  const combined = [].concat(...arrays);
  return [...new Set(combined)];
}
 
// Alternative solution
function mergeUnique(...arrays) {
  return [...new Set(arrays.flat())];
}

Task 2: Updating object

Condition: Create a function for updating a user that takes the original user object and an updates object, returning a new object with updated data and timestamp.

function updateUserWithTimestamp(user, updates) {
  // Your code here
  // Should add updatedAt field with current date
}
 
const user = {id: 1, name: 'John', email: 'john@example.com'};
const updates = {name: 'John Smith', city: 'New York'};
 
const result = updateUserWithTimestamp(user, updates);
console.log(result);
// {id: 1, name: 'John Smith', email: 'john@example.com', city: 'New York', updatedAt: ...}
Solution
function updateUserWithTimestamp(user, updates) {
  return {
    ...user,
    ...updates,
    updatedAt: new Date()
  };
}

Task 3: Function with optional parameters

Condition: Create a function for creating HTML elements that takes a tag, content, and any number of attributes.

function createElement(tag, content, ...attributes) {
  // Your code here
  // attributes — array of&nbsp;objects like {name: 'class', value: 'button'}
}
 
console.log(createElement('div', 'Hello world'));
// '<div>Hello world</div>'
 
console.log(createElement('button', 'Click me', 
  {name: 'class', value: 'btn'}, 
  {name: 'id', value: 'submit-btn'}
));
// '<button class="btn" id="submit-btn">Click me</button>'
Solution
function createElement(tag, content, ...attributes) {
  const attrs = attributes
    .map(attr => `${attr.name}="${attr.value}"`)
    .join(' ');
  
  const attrString = attrs ? ` ${attrs}` : '';
  return `<${tag}${attrString}>${content}</${tag}>`;
}

Conclusion

Spread and rest operators are powerful tools of modern JavaScript that significantly simplify working with arrays, objects, and functions. They promote writing cleaner, more readable, and functional code.

Key advantages:

  • Immutability — creating new structures without changing originals
  • Flexibility — working with variable number of parameters
  • Readability — more understandable and concise syntax
  • Functionality — supporting functional programming style

Understanding the differences between spread and rest, as well as knowing best practices for their use, will help you write more efficient and maintainable code.


Want more articles for interview preparation? Subscribe to EasyAdvice, bookmark the site and improve every day 💪