Problem Solved

JavaScript · spread vs rest

Home › Courses › JavaScript › Problems › Spread vs rest
🟨 Concept · 3 min read

Spread vs rest.
Same dots. Opposite purpose.

Both use .... Both are called "the spread operator" in casual speech. But they do opposite things. Spread expands a collection into individual elements. Rest collects individual elements into an array. The dots look identical; the context decides which one it is. This page covers both, when each applies, and the patterns you'll use every day.

Short answer: ... is spread when it appears in a function call, array literal, or object literal — it expands a collection into elements. ... is rest when it appears in a function parameter list or destructuring pattern — it collects elements into an array. Same syntax, opposite direction. Spread copies and merges. Rest collects remaining elements. Both are shallow — nested objects and arrays are still shared references.

✅ Spread — expands

Takes a collection, spreads it into elements.

spread.js
const nums = [1, 2, 3];
const moreNums = [4, 5];

// Array literal — combine arrays
const combined = [...nums, ...moreNums];
console.log(combined);
// [1, 2, 3, 4, 5]

// Array literal — copy
const copy = [...nums];
copy.push(99);
console.log(nums); // [1, 2, 3] — unchanged
console.log(copy); // [1, 2, 3, 99]

// Object literal — merge
const user = { name: 'Alice', age: 30 };
const updated = { ...user, age: 31 };
console.log(updated);
// { name: 'Alice', age: 31 }

// Function call — spread as arguments
const max = Math.max(...nums);
console.log(max); // 3

const all = [...nums, 4, 5];
console.log(all);
// [1, 2, 3, 4, 5]
Spread in three contexts:

1. Array literal — [...arr]
→ expands arr into individual elements
→ can combine multiple arrays
→ creates a new array

2. Object literal — { ...obj }
→ expands obj into properties
→ later properties overwrite earlier ones
→ creates a new object

3. Function call — fn(...arr)
→ passes each element as a separate argument
→ equivalent to fn(arr[0], arr[1], arr[2])

The key: spread is about taking something
collected and expanding it.

Common patterns:

✅ Copy an array:
const copy = [...original];

✅ Combine arrays:
const all = [...a, ...b, ...c];

✅ Copy and modify an object:
const updated = { ...obj, key: newValue };

✅ Pass an array as arguments:
fn(...args);

✅ Clone with an additional element:
const more = [...arr, newItem];

All of these are shallow. Nested objects and
arrays are still the same references.

⚠️ The syntax is the same as rest — only
the context tells you which one it is.

✅ Rest — collects

Takes individual elements, collects them into an array.

rest.js
// Function parameter — variable arguments
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15

// With other parameters — rest must be last
function greet(greeting, ...names) {
return names.map(n => ${greeting}, ${n});
}
console.log(greet('Hi', 'Alice', 'Bob'));
// ['Hi, Alice', 'Hi, Bob']

// Array destructuring — remaining elements
const [first, ...rest] = [1, 2, 3, 4];
console.log(first); // 1
console.log(rest); // [2, 3, 4]

// Object destructuring — remaining properties
const { name, ...others } = {
name: 'Alice',
age: 30,
city: 'Lahore'
};
console.log(name); // 'Alice'
console.log(others); // { age: 30, city: 'Lahore' }
Rest in three contexts:

1. Function parameters — fn(...args)
→ captures all remaining arguments
→ args is a real array
→ must be the last parameter

2. Array destructuring — [a, ...rest]
→ captures remaining elements
→ rest is an array

3. Object destructuring — { a, ...rest }
→ captures remaining properties
→ rest is a plain object

The key: rest is about taking things that
are separate and collecting them together.

Common patterns:

✅ Variable-argument functions:
function log(...args) { console.log(...args); }

✅ Extract and keep the rest:
const { password, ...safeUser } = user;

✅ Pass remaining props to a child:
const { children, ...rest } = props;
return 
{children}
; ✅ Skip known items in an array: const [first, ...others] = items; ⚠️ Rest must be last: function fn(...args, last) {} // SyntaxError const [a, ...rest, b] = [1,2,3]; // SyntaxError ⚠️ Rest is a real array, not arguments: function fn(...args) { args.map(x => x * 2); // works args.length; // works } Compare: arguments is array-like but not an array.

✅ The context tells you which

Same syntax, different role.

context.js
// SPREAD — appears inside a value

const copy = [...arr];
// [...arr] is a value being built.
// ...arr spreads arr into the new array.
// Spread.

fn(...arr);
// fn(...) is a call.
// ...arr spreads arr into arguments.
// Spread.

const obj = { ...source };
// { ...source } is a value being built.
// ...source spreads properties.
// Spread.

// REST — appears in a pattern

function fn(...args) {}
// ...args is a parameter declaration.
// It collects arguments into args.
// Rest.

const [...arr] = source;
// ...arr is a destructuring target.
// It collects remaining elements.
// Rest.

const { a, ...rest } = obj;
// ...rest is a destructuring target.
// It collects remaining properties.
// Rest.

// The rule:
// In a position that CREATES a value → spread
// In a position that CONSUMES a value → rest
// (This is not a spec rule — it's a mnemonic.)
How to tell:

Ask: is the ... on the left of an = or
inside a function's parameter list?
→ Rest. It's collecting.

Ask: is the ... on the right of an = ,
inside a function call, or inside
an array/object literal being built?
→ Spread. It's expanding.

Examples:

function fn(...args) → rest (parameter)
fn(...args) → spread (call)
const [...rest] = arr → rest (pattern)
const copy = [...arr] → spread (value)
const { a, ...rest } = o → rest (pattern)
const merged = { ...o } → spread (value)

Both use the same dots.
Both use the same characters.
Context is everything.

The mental model:
Spread: "unpack this collection"
Rest: "pack these into a collection"

Once you know which direction you want,
the context tells you which syntax applies.
In practice, you don't think about it —
you write what you mean, and the parser
does the right thing.

Don't confuse spread with Object.assign for mutation. Object.assign(target, source) mutates target. Spread creates a new object: { ...target, ...source }. If you're updating state in React, spread is correct — mutation breaks change detection. If you're writing a utility that needs to populate an existing object (like merging options into a config), Object.assign is fine and slightly faster. The rule: spread for new objects, Object.assign only when you specifically want to mutate an existing object in place. In modern code, spread wins almost every time.

⚠️ Common mistakes

The spread/rest mistakes that cost real time.

1. Rest not last in parameters

function fn(...args, x) {} — SyntaxError. Fix: rest must be the last parameter.

2. Assuming spread deep-copies

It's shallow — nested objects are still shared. Fix: use structuredClone for deep copies.

3. Spreading a non-iterable

[...{ a: 1 }] — TypeError. Objects aren't iterable. Fix: [Object.values(obj)] or spread into an object.

4. Spreading a string when you meant an array

[...'abc'] gives ['a', 'b', 'c']. Fix: use ['abc'] if you want one string.

5. Using spread on large arrays

Spread creates a new array. For million-element arrays, this is slow. Fix: pass the array directly if the API accepts one.

6. Spreading arguments in non-strict mode

Deprecated pattern. Fix: use rest parameters.

7. Using spread to copy and then mutate

const copy = [...arr]; copy.push(x); — works, but consider map or filter. Fix: use the operation that matches intent.

8. Spread on null or undefined

[...null] — TypeError. Fix: [...(arr ?? [])].

❓ Frequently asked

What is the difference between spread and rest in JavaScript?

They use the same syntax (...) but do opposite things. Spread expands an iterable into individual elements: fn(...arr) passes each element as a separate argument. Rest collects individual elements into an array: function fn(...args) captures all arguments into an array. Spread is used in function calls, array literals, and object literals. Rest is used in function parameters and destructuring.

How do I use spread to copy an array?

const copy = [...original]. This creates a new array with all the elements of the original. It's a shallow copy — the elements are the same references. For nested objects or arrays, you need a deep copy (structuredClone, or a library).

How do I use spread to merge objects?

const merged = { ...obj1, ...obj2 }. This creates a new object with all the properties of both. If both objects have the same property, the later one wins. Spread doesn't deep-merge — nested objects are still references. For deep merge, use a library like lodash.merge.

What is the rest parameter?

The rest parameter captures all remaining arguments into an array: function fn(first, ...rest) { console.log(rest); }. It's how you write functions that accept a variable number of arguments. The rest parameter must be last in the parameter list. It replaces the old arguments object, and unlike arguments, it's a real array.

Can I use rest in destructuring?

Yes. In array destructuring: const [first, ...rest] = [1, 2, 3, 4]. In object destructuring: const { a, ...others } = { a: 1, b: 2, c: 3 }. This is the pattern for extracting specific properties and keeping the rest. Very common in React.

What is the difference between spread and Object.assign?;

They do the same thing for shallow merging objects, but with different syntax and one key difference. Object.assign(target, source) mutates the target object and returns it. Spread creates a new object. Use spread unless you specifically want to mutate the target.

🔗 Continue learning

🗺️ JavaScript learning path
The full roadmap from variables to async — every concept in order

Next in this series:

🔗 Destructuring explained
Object and array destructuring, defaults, and nested patterns
← All JavaScript problems
Problem solved · JavaScript