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:
[...array]
, {...object}
)function(...args)
, [first, ...rest]
)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.
// 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
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);
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]
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
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
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}
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: ...}
// 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']
// 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!'
// 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
// 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]
// 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
// 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
// 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()
};
}
// 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);
}
// 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
Aspect | Spread | Rest |
---|---|---|
Syntax | ...expression | ...parameter |
Position | Right side of expression | Left side, parameters |
Function | Expands elements | Collects elements |
Arrays | [...arr1, ...arr2] | [first, ...rest] |
Objects | {...obj1, ...obj2} | {prop, ...others} |
Functions | fn(...args) | function(...params) |
Result | New data structure | Array or object |
Application | Copying, merging | Collecting parameters |
// 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 : [])
];
}
// 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'});
// 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
// ❌ 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 accumulation
let result = [];
for (let i = 0; i < 10000; i++) {
result.push(i);
}
// ✅ Or use spread for 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);
// ❌ 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
}
// ❌ Unsafe rest usage
function unsafeFunction(...args) {
return args[0].toUpperCase(); // Error if args[0] is not a string
}
// ✅ Parameter validation
function safeFunction(...args) {
if (args.length === 0) {
throw new Error('At least one argument is required');
}
const [first] = args;
if (typeof first !== 'string') {
throw new Error('First argument must be a string');
}
return first.toUpperCase();
}
// ✅ Using default values
function createUser({name, email, ...options} = {}) {
if (!name || !email) {
throw new Error('Name and email are required');
}
return {
name,
email,
isActive: true,
createdAt: new Date(),
...options
};
}
// Safe spreading of 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
};
// Creating public API from object with private data
function createPublicUser(userData) {
const {password, internalId, ...publicData} = userData;
return {
...publicData,
id: internalId,
hasPassword: Boolean(password)
};
}
// 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);
}
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']
function mergeUnique(...arrays) {
const combined = [].concat(...arrays);
return [...new Set(combined)];
}
// Alternative solution
function mergeUnique(...arrays) {
return [...new Set(arrays.flat())];
}
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: ...}
function updateUserWithTimestamp(user, updates) {
return {
...user,
...updates,
updatedAt: new Date()
};
}
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 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>'
function createElement(tag, content, ...attributes) {
const attrs = attributes
.map(attr => `${attr.name}="${attr.value}"`)
.join(' ');
const attrString = attrs ? ` ${attrs}` : '';
return `<${tag}${attrString}>${content}</${tag}>`;
}
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:
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 💪