JS Spread vs Rest Operator Complete Tutorial
A complete tutorial on the JavaScript spread and rest operators. Covers the difference between the two uses of the ... syntax, spread in arrays and objects, rest in function parameters and destructuring, shallow copy gotchas, and practical patterns for merging, cloning, and collecting arguments.
JavaScript uses the same three-dot syntax (...) for two distinct purposes: spread expands an iterable into individual elements, while rest collects multiple elements into a single array or object. Knowing when each applies is essential for writing clean, modern JavaScript.
Spread vs Rest at a Glance
| Feature | Syntax Location | What It Does |
|---|---|---|
| Spread | Function call arguments, array literal, object literal | Expands iterable/object into individual items |
| Rest | Function parameter list, destructuring pattern | Collects remaining items into an array/object |
The mental model: spread unpacks, rest packs.
Spread in Arrays
The spread operator expands an iterable (array, string, Set, etc.) into individual elements inside an array literal:
const front = [1, 2, 3];
const back = [7, 8, 9];
// Concatenation
const merged = [...front, ...back];
// [1, 2, 3, 7, 8, 9]
// Inserting in the middle
const withMiddle = [...front, 4, 5, 6, ...back];
// [1, 2, 3, 4, 5, 6, 7, 8, 9]
// Cloning (shallow)
const clone = [...front];
// [1, 2, 3] — a new array, not a reference to frontSpread With Strings
Strings are iterable, so spread splits them into characters:
const letters = [..."hello"];
// ["h", "e", "l", "l", "o"]Spread in Objects
Object spread copies own enumerable properties from one object into another:
const defaults = { theme: "light", lang: "en", debug: false };
const userPrefs = { theme: "dark", lang: "fr" };
// userPrefs wins — later properties override earlier ones
const config = { ...defaults, ...userPrefs };
// { theme: "dark", lang: "fr", debug: false }Order Matters for Overrides
// Defaults first, overrides second
const a = { ...defaults, ...userPrefs };
// { theme: "dark", lang: "fr", debug: false }
// Reversed — defaults would overwrite user preferences
const b = { ...userPrefs, ...defaults };
// { theme: "light", lang: "en", debug: false }Adding or Overriding Specific Properties
const user = { name: "Alice", role: "viewer" };
const admin = { ...user, role: "admin", lastLogin: new Date() };
// { name: "Alice", role: "admin", lastLogin: ... }Shallow Copy Warning
Spread creates a shallow copy. Nested objects are still shared by reference:
const original = { name: "Alice", scores: [90, 85, 92] };
const copy = { ...original };
copy.scores.push(100);
console.log(original.scores); // [90, 85, 92, 100] — mutated!For deep copies, use structuredClone() or a library. See copying nested objects with the JS spread operator for a deep dive on this topic.
Rest in Function Parameters
Rest parameters collect all remaining arguments into a real array:
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
sum(1, 2, 3); // 6
sum(10, 20, 30, 40); // 100Rest Must Be the Last Parameter
function tag(label, ...values) {
// label = first argument
// values = everything else as an array
return `${label}: ${values.join(", ")}`;
}
tag("scores", 90, 85, 72);
// "scores: 90, 85, 72"| Rule | Example | Valid? |
|---|---|---|
| Rest as only parameter | (...args) | Yes |
| Rest as last parameter | (a, b, ...rest) | Yes |
| Rest before other params | (...rest, a) | No (SyntaxError) |
| Multiple rest params | (...a, ...b) | No (SyntaxError) |
Rest in Destructuring
Array Destructuring Rest
const [first, second, ...remaining] = [10, 20, 30, 40, 50];
// first=10, second=20, remaining=[30, 40, 50]Object Destructuring Rest
const { name, age, ...extras } = {
name: "Alice",
age: 30,
role: "admin",
team: "engineering",
};
// name="Alice", age=30, extras={ role: "admin", team: "engineering" }This is a clean way to separate known properties from everything else.
Rest vs arguments Object
Rest parameters replaced the legacy arguments object:
// Legacy (not a real array, no arrow function support)
function legacySum() {
return Array.from(arguments).reduce((t, n) => t + n, 0);
}
// Modern (real array, works with arrows)
const modernSum = (...nums) => nums.reduce((t, n) => t + n, 0);| Feature | arguments | Rest ...params |
|---|---|---|
| Type | Array-like object | Real Array |
| Arrow functions | Not available | Works |
| Named alongside | Includes all args | Only uncaptured args |
| Array methods | Need Array.from() first | Direct .map(), .filter(), etc. |
Spread in Function Calls
Spread expands an array into individual arguments:
const numbers = [5, 12, 3, 8, 1];
// Instead of Math.max.apply(null, numbers)
const max = Math.max(...numbers); // 12
const min = Math.min(...numbers); // 1This replaces Function.prototype.apply() for most use cases. See JS bind call and apply methods full tutorial for the historical context.
Practical Patterns
Immutable Array Operations
const items = ["a", "b", "c", "d"];
// Remove at index 2 (immutable)
const without = [...items.slice(0, 2), ...items.slice(3)];
// ["a", "b", "d"]
// Insert at index 2 (immutable)
const withInsert = [...items.slice(0, 2), "X", ...items.slice(2)];
// ["a", "b", "X", "c", "d"]
// Replace at index 1 (immutable)
const replaced = [...items.slice(0, 1), "Z", ...items.slice(2)];
// ["a", "Z", "c", "d"]Merge With Deduplication
const a = [1, 2, 3];
const b = [3, 4, 5];
const unique = [...new Set([...a, ...b])];
// [1, 2, 3, 4, 5]Object Property Omission
function omit(obj, ...keys) {
const keysToRemove = new Set(keys);
return Object.fromEntries(
Object.entries(obj).filter(([k]) => !keysToRemove.has(k))
);
}
const user = { name: "Alice", password: "secret", role: "admin" };
const safe = omit(user, "password");
// { name: "Alice", role: "admin" }Conditional Spread
const includeDebug = true;
const config = {
host: "localhost",
port: 3000,
...(includeDebug && { debug: true, verbose: true }),
};
// { host: "localhost", port: 3000, debug: true, verbose: true }When the condition is false, the expression evaluates to false, and spreading false produces no properties.
Rune AI
Key Insights
- Spread unpacks, rest packs: Same
...syntax, but spread expands into individual items while rest collects into an array or object - Spread creates shallow copies: Nested objects remain shared by reference; use
structuredClone()for deep copies - Rest replaces the arguments object: Rest parameters are real arrays with full array method support and work in arrow functions
- Object spread order determines overrides: Later properties win, so place defaults first and overrides second
- Conditional spread is a clean pattern:
...(condition && { key: value })adds properties only when the condition is true
Frequently Asked Questions
Can I spread a Map or Set into an object?
Does spread trigger getters?
Can I use rest in a for...of loop?
Is there a performance cost to spreading large arrays?
What happens when I spread null or undefined in an object?
Conclusion
Spread and rest are two sides of the same ... coin. Spread unpacks iterables and objects into positions where multiple values are expected. Rest collects remaining values into arrays or objects. Together, they replace arguments, apply(), concat(), and manual property copying with clean declarative syntax. For related destructuring patterns, see renaming variables during JS destructuring guide and advanced array and object destructuring guide.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.