Learning Objectives

  • Understand spread vs rest operators
  • Use spread with arrays and objects
  • Apply rest parameters in functions
  • Clone and merge data structures
  • Master practical patterns

Spread vs Rest: Same Syntax, Different Purpose

Both use three dots (...), but they work in opposite ways:

// Spread - expands array
const arr = [1, 2, 3];
console.log(...arr); // 1 2 3

// Rest - collects into array
function sum(...numbers) {
    return numbers.reduce((a, b) => a + b);
}

Spread Operator with Arrays

Copy Arrays

const original = [1, 2, 3];
const copy = [...original];

copy.push(4);
console.log(original); // [1, 2, 3] - unchanged
console.log(copy);     // [1, 2, 3, 4]

Merge Arrays

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// Old way
const merged = arr1.concat(arr2);

// With spread
const merged = [...arr1, ...arr2];
console.log(merged); // [1, 2, 3, 4, 5, 6]

// Insert in middle
const combined = [...arr1, 99, ...arr2];
console.log(combined); // [1, 2, 3, 99, 4, 5, 6]

Add Elements

const numbers = [2, 3, 4];

// Add to beginning
const withStart = [1, ...numbers];
console.log(withStart); // [1, 2, 3, 4]

// Add to end
const withEnd = [...numbers, 5];
console.log(withEnd); // [2, 3, 4, 5]

// Add to both
const withBoth = [0, ...numbers, 5, 6];
console.log(withBoth); // [0, 2, 3, 4, 5, 6]

Convert to Array

// String to array
const str = 'hello';
const chars = [...str];
console.log(chars); // ['h', 'e', 'l', 'l', 'o']

// NodeList to array
const divs = document.querySelectorAll('div');
const divsArray = [...divs];

// Set to array
const set = new Set([1, 2, 3]);
const arr = [...set];

Math Operations

const numbers = [5, 2, 8, 1, 9];

console.log(Math.max(...numbers)); // 9
console.log(Math.min(...numbers)); // 1

// Without spread (old way)
console.log(Math.max.apply(null, numbers));

Spread Operator with Objects

Copy Objects

const original = { name: 'John', age: 30 };
const copy = { ...original };

copy.age = 31;
console.log(original.age); // 30 - unchanged
console.log(copy.age);     // 31

Merge Objects

const defaults = { theme: 'light', language: 'en' };
const userPrefs = { theme: 'dark' };

const settings = { ...defaults, ...userPrefs };
console.log(settings);
// { theme: 'dark', language: 'en' }
// userPrefs overwrites defaults

Add/Update Properties

const user = { name: 'John', age: 30 };

// Add property
const withEmail = { ...user, email: 'john@example.com' };

// Update property
const olderUser = { ...user, age: 31 };

// Add multiple
const fullUser = {
    ...user,
    email: 'john@example.com',
    country: 'USA'
};

Conditional Properties

const includeEmail = true;

const user = {
    name: 'John',
    age: 30,
    ...(includeEmail && { email: 'john@example.com' })
};

console.log(user);
// { name: 'John', age: 30, email: 'john@example.com' }

Rest Parameters

Variable Number of Arguments

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

Rest with Named Parameters

function greet(greeting, ...names) {
    return `${greeting} ${names.join(', ')}!`;
}

console.log(greet('Hello', 'John'));
// "Hello John!"

console.log(greet('Hello', 'John', 'Jane', 'Bob'));
// "Hello John, Jane, Bob!"

Rest Must Be Last

// Valid
function fn(a, b, ...rest) {}

// Invalid
// function fn(...rest, a, b) {} // SyntaxError
// function fn(a, ...rest, b) {} // SyntaxError

Real-World Examples

Immutable Array Operations

const todos = [
    { id: 1, text: 'Learn JS', done: false },
    { id: 2, text: 'Build app', done: false }
];

// Add todo
const addTodo = (todos, newTodo) => [...todos, newTodo];

// Remove todo
const removeTodo = (todos, id) => 
    todos.filter(todo => todo.id !== id);

// Update todo
const updateTodo = (todos, id, updates) =>
    todos.map(todo =>
        todo.id === id ? { ...todo, ...updates } : todo
    );

const updated = updateTodo(todos, 1, { done: true });

React State Updates

// Add item to array
setItems([...items, newItem]);

// Update object
setUser({ ...user, name: 'Jane' });

// Update nested object
setUser({
    ...user,
    address: {
        ...user.address,
        city: 'Boston'
    }
});

Function Composition

function compose(...fns) {
    return (x) => fns.reduceRight((acc, fn) => fn(acc), x);
}

const double = x => x * 2;
const addTen = x => x + 10;
const square = x => x * x;

const compute = compose(square, addTen, double);
console.log(compute(5)); // ((5 * 2) + 10)^2 = 400

API Request Builder

function buildRequest(url, options = {}) {
    const defaults = {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json'
        }
    };

    return {
        ...defaults,
        ...options,
        headers: {
            ...defaults.headers,
            ...options.headers
        }
    };
}

const request = buildRequest('/api/users', {
    method: 'POST',
    headers: { 'Authorization': 'Bearer token' }
});

Logger Function

function log(level, ...messages) {
    const timestamp = new Date().toISOString();
    console.log(`[${timestamp}] [${level}]`, ...messages);
}

log('INFO', 'User logged in');
log('ERROR', 'Failed to fetch', { userId: 123 });

Merge Configurations

const defaultConfig = {
    port: 3000,
    host: 'localhost',
    database: {
        host: 'localhost',
        port: 5432
    }
};

const userConfig = {
    port: 8080,
    database: {
        host: 'db.example.com'
    }
};

// Deep merge
const config = {
    ...defaultConfig,
    ...userConfig,
    database: {
        ...defaultConfig.database,
        ...userConfig.database
    }
};

Performance Considerations

Shallow Copy Only

Spread creates shallow copies. Nested objects/arrays are still referenced:

const original = { user: { name: 'John' } };
const copy = { ...original };

copy.user.name = 'Jane';
console.log(original.user.name); // 'Jane' - modified!
Performance with Large Arrays

Spread is fast for small to medium arrays, but for very large arrays, consider other methods:

// For large arrays
const merged = arr1.concat(arr2); // Faster
// vs
const merged = [...arr1, ...arr2]; // Slower for huge arrays

Common Patterns

Remove Item from Array

const items = [1, 2, 3, 4, 5];
const index = 2;

const newItems = [
    ...items.slice(0, index),
    ...items.slice(index + 1)
];
console.log(newItems); // [1, 2, 4, 5]

Toggle Boolean Property

const user = { name: 'John', active: false };

const toggled = { ...user, active: !user.active };

Flatten Array One Level

const nested = [[1, 2], [3, 4], [5, 6]];
const flat = [].concat(...nested);
console.log(flat); // [1, 2, 3, 4, 5, 6]

// Or use flat()
const flat = nested.flat();

Key Takeaways