# Spread vs Rest Operators in JavaScript: Expanding vs Collecting

**Audience:** This post assumes basic familiarity with JavaScript arrays, objects, and functions.

![](https://cdn.hashnode.com/uploads/covers/6185effafd5d634d0169926f/e7897faf-bdcc-4011-9646-3a7f8ed31a5a.svg align="right")

**TL;DR:** Both spread (`...`) and rest (`...`) use the same three-dot syntax — but they do the opposite thing depending on context. Spread *expands* an **iterable into individual elements**. Rest *collects* **individual elements into a single array**. Knowing when you're doing which eliminates an entire class of confusion.

* * *

## Problem

JavaScript's `...` syntax is one of the most commonly misread features in the language. Beginners (and sometimes experienced devs) write `...args` in a function call and `...args` in a function signature, expecting the same behavior and get confused when the results differ.

They're not the same. Same punctuation, opposite direction of data flow.

* * *

## The Core Mental Model

Think of data flow direction:

*   **Spread** → one thing becomes many (`[1, 2, 3]` → `1, 2, 3`)
    
*   **Rest** → many things become one (`1, 2, 3` → `[1, 2, 3]`)
    

The position in code tells you which one you're dealing with:

| Context | Behavior | Name |
| --- | --- | --- |
| Function **call** | Expands iterable into arguments | Spread |
| Array/object **literal** | Expands iterable into elements | Spread |
| Function **definition** | Collects remaining args into array | Rest |
| Destructuring **assignment** | Collects remaining items | Rest |

* * *

## Solution

### Spread operator

Spread takes something iterable — an array, a string, a Set — and unpacks its contents in-place.

**With arrays:**

```js
const first = [1, 2, 3];
const second = [4, 5, 6];

const combined = [...first, ...second];
console.log(combined); // [1, 2, 3, 4, 5, 6]

// Equivalent to:
// [].concat(first, second)
// But cleaner and works with any iterable
```

**With objects:**

Spread copies all enumerable own properties from one object into another. Later keys win — this is how you do "merge with override."

```js
const defaults = { theme: "light", language: "en", timeout: 3000 };
const userPrefs = { theme: "dark", language: "fr" };

const config = { ...defaults, ...userPrefs };
console.log(config);
// { theme: "dark", language: "fr", timeout: 3000 }
// userPrefs.theme overrides defaults.theme
```

**In function calls:**

```js
const numbers = [3, 1, 4, 1, 5, 9];

// Math.max doesn't accept arrays — only individual numbers
console.log(Math.max(...numbers)); // 9

// Without spread you'd need: Math.max.apply(null, numbers)
```

**Important:** Spread creates a shallow copy, not a deep one.

```js
const original = { user: { name: "Alice" } };
const copy = { ...original };

copy.user.name = "Bob";
console.log(original.user.name); // "Bob" — same nested object reference
```

* * *

### Rest operator

Rest collects multiple values into a single array. It only appears in two places: function parameter lists and destructuring assignments.

**In function parameters:**

```js
function logMessages(level, ...messages) {
  // level: string — the first argument
  // messages: array — all remaining arguments
  messages.forEach(msg => console.log(`[${level}] ${msg}`));
}

logMessages("INFO", "Server started", "Listening on port 3000");
// [INFO] Server started
// [INFO] Listening on port 3000
```

Rest must always be the **last** parameter. This throws:

```js
// SyntaxError: Rest parameter must be last formal parameter
function broken(...items, last) {}
```

**In array destructuring:**

```js
const [head, ...tail] = [10, 20, 30, 40];

console.log(head); // 10
console.log(tail); // [20, 30, 40]
```

**In object destructuring:**

```js
const { id, createdAt, ...publicFields } = {
  id: 42,
  createdAt: "2024-01-01",
  name: "Alice",
  role: "admin",
};

console.log(publicFields); // { name: "Alice", role: "admin" }
// Useful for stripping internal fields before sending to a client
```

* * *

### Practical use cases

**1\. Immutable array updates (React state)**

```js
function addItem(cart, newItem) {
  return [...cart, newItem]; // doesn't mutate cart
}

function removeItem(cart, id) {
  return cart.filter(item => item.id !== id);
}

function updateItem(cart, updatedItem) {
  return cart.map(item =>
    item.id === updatedItem.id ? { ...item, ...updatedItem } : item
  );
}
```

**2\. Merging config with defaults**

```js
function createServer(userConfig = {}) {
  const defaults = {
    port: 3000,
    host: "localhost",
    timeout: 5000,
    cors: false,
  };

  const config = { ...defaults, ...userConfig };

  return config;
}

const server = createServer({ port: 8080, cors: true });
console.log(server);
// { port: 8080, host: "localhost", timeout: 5000, cors: true }
```

**3\. Variadic utility functions**

```js
function sum(...numbers) {
  return numbers.reduce((acc, n) => acc + n, 0);
}

console.log(sum(1, 2, 3));       // 6
console.log(sum(10, 20, 30, 40)); // 100
```

**4\. Cloning and extending objects**

```js
const baseButton = {
  padding: "8px 16px",
  borderRadius: "4px",
  cursor: "pointer",
};

const primaryButton = {
  ...baseButton,
  backgroundColor: "blue",
  color: "white",
};

const dangerButton = {
  ...baseButton,
  backgroundColor: "red",
  color: "white",
};
```

**5\. Converting non-arrays to arrays**

```js
function highlightAll(selector) {
  const nodes = document.querySelectorAll(selector); // NodeList, not Array
  const elements = [...nodes]; // now a real Array with .map, .filter, etc.

  elements.forEach(el => el.classList.add("highlight"));
}
```

* * *

## Results

Using spread and rest correctly eliminates several common patterns that are either verbose or error-prone:

| Old pattern | Modern equivalent |
| --- | --- |
| `[].concat(a, b)` | `[...a, ...b]` |
| `Object.assign({}, defaults, overrides)` | `{ ...defaults, ...overrides }` |
| `Math.max.apply(null, arr)` | `Math.max(...arr)` |
| `Array.from(nodeList)` | `[...nodeList]` |
| `arguments` object | `...args` rest parameter |

The `arguments` object was particularly painful — it's array-like but lacks `.map()`, `.filter()`, and friends. Rest parameters give you a real array from the start.

* * *

## Trade-offs

**Spread is shallow.** It copies property references, not values. For deeply nested structures you still need `structuredClone()` or a library like `lodash.cloneDeep`.

```js
const a = { nested: { value: 1 } };
const b = { ...a };
b.nested.value = 99;
console.log(a.nested.value); // 99 — a was mutated
```

**Object spread order matters.** When the same key appears in multiple sources, the last one wins. A wrong merge order silently discards data.

```js
// userConfig overrides everything — intended
const config = { ...defaults, ...userConfig };

// defaults override userConfig — almost certainly a bug
const config = { ...userConfig, ...defaults };
```

**Spread on large arrays has memory cost.** Spreading a 100,000-element array into a function call creates a new 100,000-argument invocation on the call stack. For large datasets, prefer explicit iteration.

**Rest collects by position, not by name.** In destructuring, `...rest` captures everything *after* the named bindings — positional for arrays, by key exclusion for objects.

* * *

## Conclusion

The `...` syntax encodes direction: *outward* when spreading, *inward* when resting. Read the context — a definition or destructuring means rest, a literal or call means spread.

The practical payoff is cleaner, more composable code: immutable array/object transforms without `concat` or `Object.assign`, variadic functions without the `arguments` hack, and selective destructuring without manual key exclusion.

Next step: combine them. A function that both uses rest in its signature and spread in its body is idiomatic JavaScript:

```js
function merge(...objects) {
  return Object.assign({}, ...objects);
}
```

* * *

## Further reading

*   [MDN: Spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax)
    
*   [MDN: Rest parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters)
    
*   [MDN: Destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)
    
*   [JavaScript.info: Rest parameters and spread](https://javascript.info/rest-parameters-spread)
    
*   [You Don't Know JS: ES6 & Beyond — Chapter 2](https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/es6%20%26%20beyond/ch2.md)
