# Spread and Rest Operators

Both use the same `...` syntax but do **opposite** things depending on context: spread **expands** a collection into individual elements; rest **collects** individual elements into a collection.

![Spread expands one array into three individual elements; rest collects three individual arguments back into one array](diagrams/01-spread-vs-rest.svg)

## 1. Spread in Array Literals

```js
const a = [1, 2];
const b = [3, 4];
const combined = [...a, ...b];        // [1, 2, 3, 4]
const withExtra = [0, ...a, 2.5];      // [0, 1, 2, 2.5]
const copy = [...a];                    // shallow copy, not the same array reference
```

## 2. Spread in Object Literals

```js
const base = { theme: "light", retries: 3 };
const merged = { ...base, retries: 5 }; // { theme: "light", retries: 5 } — later keys win
const copy = { ...base };                // shallow copy
```

Both array and object spread are **shallow**: nested objects/arrays inside are still shared by reference (identical caveat to `Object.assign`, covered in the Objects topic).

## 3. Spread in Function Calls

```js
function sum3(a, b, c) { return a + b + c; }
const nums = [1, 2, 3];
sum3(...nums); // 6 — equivalent to sum3(1, 2, 3)

Math.max(...[4, 8, 1]); // 8 — Math.max doesn't accept an array directly, spread fixes that
```

## 4. Rest in Function Parameters

```js
function sum(...numbers) { // collects ALL remaining arguments into a real array
  return numbers.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4); // 10

function logFirst(first, ...rest) { // rest collects everything AFTER named params
  console.log(first, rest);
}
logFirst(1, 2, 3); // 1  [2, 3]
```

Rest parameters must be the **last** parameter — `function f(...rest, last) {}` is a syntax error.

## 5. Rest in Destructuring

```js
const [head, ...tail] = [1, 2, 3, 4];   // head=1, tail=[2,3,4]
const { id, ...otherFields } = { id: 1, name: "Ana", age: 30 };
// id=1, otherFields={ name: "Ana", age: 30 }
```

Same last-position rule applies here too.

## 6. Telling Spread and Rest Apart

| Context | `...` means |
|---|---|
| Inside `[...]` or `{...}` literal, or as a function call argument | **spread** — expand |
| As a function parameter, or on the left side of destructuring | **rest** — collect |

```js
function example(...args) {   // rest: args becomes an array
  console.log(Math.max(...args)); // spread: expand args back out for Math.max
}
```

## Under the Hood: Two Genuinely Different Mechanisms Behind One Symbol

Array spread and object spread look identical at the syntax level, but they are implemented by **completely different internal operations** — this is worth knowing precisely, not just as trivia, because it explains what each one can and can't do.

### Array spread runs the iterator protocol, for real

```js
const arr = [...someIterable];
```

Spreading inside `[...]` (or as function-call arguments) literally invokes `someIterable[Symbol.iterator]()` and calls `.next()` repeatedly until `done: true` — the exact mechanism from the Iterators and Generators topic. This is why spread works on **any** iterable (strings, `Map`, `Set`, generators, custom iterable classes), and why spreading a non-iterable value (`[...{ a: 1 }]`, a plain object) throws `TypeError: is not iterable` — there's no `Symbol.iterator` method for the engine to call.

```js
function* naturalNumbers() { let n = 1; while (n <= 3) yield n++; }
console.log([...naturalNumbers()]); // [1, 2, 3] — spread drives the generator via real .next() calls
```

### Object spread does NOT use iteration at all

```js
const merged = { ...base, ...overrides };
```

`{...obj}` is specified as an internal `CopyDataProperties` operation: it directly enumerates `obj`'s **own enumerable properties** (the same set `Object.keys` would return) and copies each one — no `Symbol.iterator` involved whatsoever. This is why object spread works on plain objects (which aren't iterable) but a `Map` spread with `{...someMap}` does **not** copy its entries the way `[...someMap]` would — `{...}` only ever sees a `Map` instance's own enumerable properties, which is essentially none of its actual entries (they're stored internally, not as regular properties — see the Map topic).

### Rest parameters allocate a real new array, every call

```js
function sum(...numbers) { /* ... */ }
```

Every single call to `sum` allocates a fresh array object to hold the collected arguments — this isn't free, though it's cheap in absolute terms and, crucially, it's a **real** array (unlike the legacy `arguments` object from the Functions topic), so it doesn't carry that object's historical optimization penalties.

### Why spreading enormous arrays into a call can fail

```js
fn(...hugeArray); // hugeArray with, say, 200,000 elements
```

Function calls have an engine-enforced maximum argument count (well into the tens of thousands, but finite) — spread turns each array element into a genuine, individual function argument at the call site, and exceeding that limit throws `RangeError: Maximum call stack size exceeded` or a similar limit error, because arguments occupy real stack space per the Call Stack topic. Chunking the array or using `.apply()` with careful batching avoids this; regular iteration (`for`, `.reduce()`) never hits the limit because it never turns array elements into call arguments at all.

## 7. Common Pitfalls

1. **Assuming spread deep-copies** — it's shallow, exactly like `Object.assign`; nested structures remain shared references.
2. **Spreading a `Set`/`Map`/string when an array is expected elsewhere** — spread works on any iterable, not just arrays: `[..."abc"]` gives `["a", "b", "c"]`.
3. **Using rest anywhere but the last position** — always a syntax error, whether in function parameters or destructuring patterns.
4. **Spreading huge arrays into function calls** — `fn(...hugeArray)` can hit engine argument-count limits (tens of thousands of elements); use `.apply()` or chunking for very large collections.
5. **Object spread key order** — later spread sources overwrite earlier ones for duplicate keys, left to right; getting the order backwards silently produces the wrong merge result.

## 8. Quick Reference

```
[...arr]        → spread: expand array into elements
{...obj}         → spread: expand object into key-values
fn(...args)       → spread: expand array into arguments
function f(...a)   → rest: collect arguments into array
const [x, ...y] = arr    → rest: collect remaining into array
const {x, ...y} = obj     → rest: collect remaining into object
```
