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.

JavaScriptintermediate
12 min read

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

FeatureSyntax LocationWhat It Does
SpreadFunction call arguments, array literal, object literalExpands iterable/object into individual items
RestFunction parameter list, destructuring patternCollects 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:

javascriptjavascript
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 front

Spread With Strings

Strings are iterable, so spread splits them into characters:

javascriptjavascript
const letters = [..."hello"];
// ["h", "e", "l", "l", "o"]

Spread in Objects

Object spread copies own enumerable properties from one object into another:

javascriptjavascript
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

javascriptjavascript
// 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

javascriptjavascript
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:

javascriptjavascript
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:

javascriptjavascript
function sum(...numbers) {
  return numbers.reduce((total, n) => total + n, 0);
}
 
sum(1, 2, 3);       // 6
sum(10, 20, 30, 40); // 100

Rest Must Be the Last Parameter

javascriptjavascript
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"
RuleExampleValid?
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

javascriptjavascript
const [first, second, ...remaining] = [10, 20, 30, 40, 50];
// first=10, second=20, remaining=[30, 40, 50]

Object Destructuring Rest

javascriptjavascript
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:

javascriptjavascript
// 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);
FeatureargumentsRest ...params
TypeArray-like objectReal Array
Arrow functionsNot availableWorks
Named alongsideIncludes all argsOnly uncaptured args
Array methodsNeed Array.from() firstDirect .map(), .filter(), etc.

Spread in Function Calls

Spread expands an array into individual arguments:

javascriptjavascript
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); // 1

This 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

javascriptjavascript
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

javascriptjavascript
const a = [1, 2, 3];
const b = [3, 4, 5];
 
const unique = [...new Set([...a, ...b])];
// [1, 2, 3, 4, 5]

Object Property Omission

javascriptjavascript
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

javascriptjavascript
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

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
RunePowered by Rune AI

Frequently Asked Questions

Can I spread a Map or Set into an object?

No directly. Map and Set are iterable, so you can spread them into an array (`[...mySet]`), but not into an object literal. Use `Object.fromEntries([...myMap])` to convert a Map to an object.

Does spread trigger getters?

Yes. When you spread an object, each property's getter is called and the returned value is copied. The getter itself is not transferred.

Can I use rest in a for...of loop?

No. Rest syntax is only valid in function parameters and destructuring assignments. For iteration, use the loop body to collect items.

Is there a performance cost to spreading large arrays?

For small-to-medium arrays (thousands of elements), the cost is negligible. For very large arrays (millions), consider using `Array.prototype.concat()` or typed arrays, as spread creates intermediate iterators.

What happens when I spread null or undefined in an object?

`{ ...null }` and `{ ...undefined }` both produce an empty object. No error is thrown. In array context, `[...null]` throws a TypeError because null is not iterable.

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.