Spread vs Rest Operator in JavaScript
Short answer
They're the same three dots doing opposite jobs. Spread expands an array or object into its individual pieces. Rest collects leftover pieces into a new array or object. The rule: on the left of an = or in a parameter list it's rest; anywhere else it's spread.
| Spread | Rest | |
|---|---|---|
| Does | Expands one thing into many | Collects many things into one |
| Appears | In calls, array/object literals | In parameters and destructuring |
| Position | Right of =, inside ( ) [ ] { } | Left of =, or last parameter |
| Produces | Individual elements | A real array or object |
| How many allowed | As many as you like | Exactly one, and it must be last |
| Example | Math.max(...nums) | function f(...nums) |
Same syntax, opposite jobs
There is only one ... in JavaScript. Whether it spreads or rests depends entirely on where you write it — which is exactly why it confuses people, and exactly why interviewers like asking about it.
1// SPREAD — one array becomes many arguments
2const nums = [1, 5, 3];
3Math.max(...nums); // → 5 (same as Math.max(1, 5, 3))
4
5// REST — many arguments become one array
6function max(...values) {
7 return Math.max(...values); // ← rest collecting, then spread expanding
8}
9max(1, 5, 3); // → 5That last example has both in three lines: ...values in the parameter list gathers the arguments into an array, then ...values inside the call expands that array back into arguments. Being able to point at the two and name each one is a clean way to show you actually understand the distinction.
The rule for telling them apart
1const [first, ...others] = [1, 2, 3, 4];
2// ^^^^^^^^ REST — left of =, collects
3// first → 1, others → [2, 3, 4]
4
5const combined = [0, ...others];
6// ^^^^^^^^ SPREAD — right of =, expands
7// → [0, 2, 3, 4]Spread: copying and merging
In practice, spread is mostly used for making copies and merging things without mutating the originals — which is why it's everywhere in React and Redux code.
1// Copy an array
2const copy = [...original]; // → new array, same elements
3
4// Merge arrays
5const all = [...a, ...b]; // → [1, 2, 3, 4]
6
7// Copy + override an object
8const updated = { ...user, name: "Arun" };
9
10// Turn a string into characters
11[..."hey"]; // → ["h", "e", "y"]
12
13// Convert a Set back to an array
14[...new Set([1, 1, 2])]; // → [1, 2] (dedupe idiom)The shallow-copy trap
This is the follow-up that catches most candidates. Spread copies one level deep. Nested objects and arrays are copied by reference, so the "copy" still shares them with the original.
1const original = { name: "Arun", address: { city: "Chennai" } };
2const copy = { ...original };
3
4copy.name = "Divya";
5console.log(original.name); // → "Arun" ✅ top level is safe
6
7copy.address.city = "Bengaluru";
8console.log(original.address.city); // → "Bengaluru" ❌ nested is SHARED1// Spread each level you care about
2const copy = { ...original, address: { ...original.address } };
3
4// Or clone the whole structure (Node 17+, all modern browsers)
5const deep = structuredClone(original);
6deep.address.city = "Bengaluru";
7console.log(original.address.city); // → "Chennai" ✅Rest: collecting what's left
Rest shows up in two places: function parameters, and destructuring. Both mean the same thing — "put everything I haven't already named in here".
1// Arrays — positional
2const [winner, runnerUp, ...rest] = ["a", "b", "c", "d"];
3// winner → "a", runnerUp → "b", rest → ["c", "d"]
4
5// Objects — by key. The idiomatic way to omit a field:
6const { password, ...safeUser } = user;
7// safeUser → every property except passwordThat object pattern is worth remembering. Stripping a sensitive field before sending a user object to the client is a genuinely common task, and rest destructuring does it in one line without mutating anything.
function f(...nums, last) {}
// → SyntaxError: Rest parameter must be last formal parameter
const [...a, ...b] = [1, 2, 3];
// → SyntaxError: Rest element must be last elementThis is 1 of 80+ questions in the JavaScript Interview Kit
Get every question with detailed answers, follow-ups and real code — plus coding challenges and a last-minute revision sheet. One-time payment, instant access.
⚡ Get the JavaScript Interview Kit → ₹299Rest in function signatures
Rest parameters are how you write a function that accepts any number of arguments, and they combine naturally with named parameters in front of them.
1function log(level, ...messages) {
2 console.log(`[${level}]`, messages.join(" "));
3}
4
5log("warn", "disk", "almost", "full");
6// → [warn] disk almost full
7
8log("info");
9// → [info] (messages is [], never undefined — a real empty array)That last detail is worth knowing: a rest parameter is always an array, even when nothing was passed. You never need to guard against it being undefined, which makes it safer than reading arguments.length. It also means length reports only the named parameters before the rest, since a rest parameter does not contribute to a function's declared arity.
1// Wrap any function without knowing its signature
2function withTiming(fn) {
3 return function (...args) { // REST — collect whatever comes in
4 const start = performance.now();
5 const result = fn(...args); // SPREAD — pass it straight through
6 console.log(`${fn.name} took ${performance.now() - start}ms`);
7 return result;
8 };
9}
10
11const timedSort = withTiming(sortUsers);
12timedSort(users, "name");
13// → sortUsers took 0.42msThis rest-then-spread pairing is the backbone of decorators, memoization helpers, retry wrappers and logging middleware. If an interviewer asks you to write a debounce, throttle or memoize function, this is the shape the answer takes — which is why the two are worth practising together rather than as separate trivia.
Rest parameters vs the arguments object
A guaranteed follow-up, because rest parameters were introduced specifically to replace arguments. The old object is array-like but not an array, and it doesn't exist at all in arrow functions.
| arguments | Rest parameters | |
|---|---|---|
| Type | Array-like object | A real array |
| Has map/filter/reduce | No | Yes |
| In arrow functions | Not available | Works normally |
| Contains | Every argument | Only the uncaptured ones |
| Named | No | Yes — you choose |
1function old() {
2 return arguments.map(n => n * 2);
3}
4old(1, 2); // → TypeError: arguments.map is not a function
5
6const modern = (...nums) => nums.map(n => n * 2);
7modern(1, 2); // → [2, 4]
8
9const broken = () => arguments;
10broken(1, 2); // → ReferenceError (or the outer scope's arguments)Spread and rest in React
Both are load-bearing in everyday React, which is why this comes up in frontend interviews specifically rather than general JavaScript ones.
1// REST — take the props you handle, forward the rest untouched
2function Button({ variant, children, ...rest }) {
3 return <button className={variant} {...rest}>{children}</button>;
4 // ^^^^^^^ SPREAD — onClick, disabled, aria-*…
5}
6
7// SPREAD — immutable state updates
8setUser(prev => ({ ...prev, name: "Arun" }));
9setItems(prev => [...prev, newItem]);
10// → a new object and a new array each time, so React sees the changeSpread vs apply — what it replaced
Before spread existed, turning an array into arguments meant Function.prototype.apply. Knowing this is useful both for reading older code and for explaining what spread is actually doing.
1const nums = [1, 5, 3];
2
3Math.max.apply(null, nums); // → 5 (pre-ES6: null is the "this" you don't need)
4Math.max(...nums); // → 5 (same thing, no fake "this" argument)
5
6// apply also had a hard argument-count ceiling; spread is subject to the
7// engine's stack limit too, so for very large arrays use reduce instead:
8nums.reduce((m, n) => Math.max(m, n), -Infinity); // → 5, safe at any sizeThe last line matters more than it looks. Spreading an array of a few hundred thousand elements into a function call can throw "Maximum call stack size exceeded", because every element becomes a real argument on the stack. It's a genuine production bug, not a trivia question.
Real patterns worth memorising
1// Conditionally include a property
2const body = { name, ...(isAdmin && { role: "admin" }) };
3// → { name } when false; { name, role } when true
4
5// Conditionally include array items
6const steps = ["start", ...(verbose ? ["log"] : []), "finish"];
7
8// Default options, caller overrides
9function init(options) {
10 const config = { retries: 3, timeout: 5000, ...options };
11}
12
13// Replace one item immutably by index
14const next = [...items.slice(0, i), updated, ...items.slice(i + 1)];
15
16// Swap two variables
17let [a, b] = [b, a];
18
19// Shallow-compare-friendly React update
20setFilters(prev => ({ ...prev, page: 1 }));What spread can and can't expand
Array spread works on any iterable — arrays, strings, Sets, Maps, NodeLists, generators. Object spread is different: it copies own enumerable properties, and works on any object.
1// Both convert an iterable to an array
2[...new Set([1, 2])]; // → [1, 2]
3Array.from(new Set([1, 2])); // → [1, 2]
4
5// Only Array.from handles array-LIKES (length + indices, not iterable)
6Array.from({ length: 3 }); // → [undefined, undefined, undefined]
7[...{ length: 3 }]; // → TypeError: object is not iterable
8
9// And only Array.from takes a map function
10Array.from({ length: 3 }, (_, i) => i * 2); // → [0, 2, 4]Are spread and rest actually operators?
Not formally. The spec calls them spread syntax and rest parameters/elements — they're grammar productions, not operators like + or typeof. Nearly everyone says "spread operator" colloquially, so it isn't worth correcting anyone, but knowing the distinction is a small mark of precision.
Does spread copy non-enumerable properties or the prototype?
No. Object spread copies only own enumerable properties, so inherited methods and non-enumerable properties are lost — spreading a class instance gives you a plain object without its prototype. Use Object.create plus Object.getOwnPropertyDescriptors, or structuredClone, if you need more fidelity.
Which JavaScript version added them?
Array spread and rest parameters landed in ES2015 (ES6). Object spread and object rest came later, in ES2018. That gap explains why older codebases use Object.assign({}, a, b) where you would now write { ...a, ...b }.
Is spreading a large array slow?
It's O(n) — fine for normal data, but avoid it inside a loop, where [...acc, item] on each iteration turns an O(n) job into O(n²). Push into an array and spread once at the end, or use concat outside the loop.
What happens if you spread null or undefined?
Into an object it's a no-op — { ...null } gives {} without throwing, which is what makes the conditional-spread trick safe. Into an array it throws a TypeError, because null isn't iterable. That asymmetry surprises people, so guard array spreads with a fallback: [...(items ?? [])].
Can you use rest with default parameter values?
Not on the rest parameter itself — function f(...args = []) is a SyntaxError, and it would be pointless anyway since a rest parameter is already an empty array when nothing is passed. Parameters before the rest can have defaults normally: function f(level = "info", ...rest) is perfectly valid.
Does spread work on Maps and Sets?
Both are iterable, so array spread works: [...mySet] gives the values, and [...myMap] gives an array of [key, value] pairs. Object spread does not — { ...myMap } produces an empty object, because a Map stores its data internally rather than as own enumerable properties. Use Object.fromEntries(myMap) instead.
Does spreading preserve getters and setters?
No. Object spread invokes a getter and copies the resulting value, so the copy holds a plain static property rather than the accessor. The same applies to Object.assign. If you need to preserve accessors, combine Object.create with Object.getOwnPropertyDescriptors, which copies the descriptors themselves rather than their current values.
Frequently asked questions
- What is the difference between the spread and rest operators?
- They use the same ... syntax but do opposite things. Spread expands an array or object into individual elements — in a function call, or an array or object literal. Rest collects remaining values into a new array or object — in a parameter list or a destructuring pattern.
- How do I know whether ... is spread or rest?
- Look at whether it's receiving or producing values. In a parameter list or on the left of an assignment it's collecting, so it's rest. Inside a function call, array literal or object literal it's expanding, so it's spread.
- Is the spread operator a deep copy?
- No, it's shallow. Top-level properties are copied, but nested objects and arrays are shared by reference, so mutating a nested value affects both. Spread each level you need, or use structuredClone() for a genuine deep copy.
- What is the difference between rest parameters and the arguments object?
- Rest parameters give you a real array with map, filter and reduce available, they're named, and they work in arrow functions. arguments is an array-like object without array methods, contains every argument rather than just the uncaptured ones, and doesn't exist in arrow functions at all.
- Why must the rest parameter be last?
- Because it means "everything remaining" — there's no way to know where it stops if more parameters followed. Writing function f(...nums, last) is a SyntaxError, as is a rest element that isn't last in a destructuring pattern.
- How do I remove a property from an object without mutating it?
- Use rest destructuring: const { password, ...safeUser } = user gives you a new object with every property except password, leaving the original untouched. It's the idiomatic one-line way to omit a field.
- Does order matter when spreading objects?
- Yes — later properties overwrite earlier ones. { ...defaults, ...options } lets options win, which is usually what you want; reversing them makes defaults override the caller's values, which is almost always a bug.
- What is the difference between spread and Array.from?
- Spread only works on iterables. Array.from also handles array-likes — objects with a length and numeric indices but no iterator — and accepts a map function as its second argument. Array.from({ length: 3 }, (_, i) => i) works; spreading that object throws.
- Can you spread a string?
- Yes, because strings are iterable: [..."hey"] gives ["h", "e", "y"]. It splits by code point rather than code unit, so it handles emoji and other astral characters better than "…".split("").
- Are spread and rest actually operators?
- Technically no — the specification calls them spread syntax and rest parameters/elements, since they're grammar constructs rather than operators like typeof. "Spread operator" is the near-universal colloquial name, so it's fine to use, but the distinction is a nice detail to know.
- How are spread and rest used in React?
- Rest collects the props a component doesn't handle itself — function Button({ variant, ...rest }) — and spread forwards them onto the element with {...rest}. Spread is also how you update state immutably: setUser(prev => ({ ...prev, name })) and setItems(prev => [...prev, item]).
- Which JavaScript version introduced spread and rest?
- Array spread and rest parameters arrived in ES2015 (ES6). Object spread and object rest came in ES2018, which is why older code uses Object.assign({}, a, b) instead of { ...a, ...b }.
This is 1 of 80+ questions in the JavaScript Interview Kit
Get every question with detailed answers, follow-ups and real code — plus coding challenges and a last-minute revision sheet. One-time payment, instant access.
⚡ Get the JavaScript Interview Kit → ₹299