Rest and Spread Syntax
Collect args with rest, expand iterables with spread — shallow copy pitfalls, parameter order, and object merge patterns.
- javascript
- rest-and
... does two jobs depending on position: spread expands iterables/objects into places expecting elements/properties; rest collects remaining elements/properties into one binding.
Function rest parameters
function sum(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3); // 6
function greet(first, ...rest) {
// rest is always a real Array
return [first, ...rest].join(' ');
}
Rest must be last in the parameter list. Prefer rest over legacy arguments (which isn’t a real array and behaves oddly with arrows).
Spread into calls and arrays
const a = [1, 2];
const b = [0, ...a, 3]; // [0,1,2,3]
Math.max(...a); // 2
// clone (shallow)
const copy = [...a];
Spread uses the iterable protocol — works on strings, Maps (keys via default iterator… careful), Sets, NodeLists after conversion patterns, etc.
[...'ab']; // ['a','b']
[...new Set([1, 1, 2])]; // [1,2]
Object spread / rest
const user = { id: 1, name: 'Ada', role: 'admin' };
const { role, ...publicUser } = user;
// publicUser { id, name }
const settings = { theme: 'dark', ...userDefaults, ...overrides };
// later keys win
Object spread is shallow: nested objects are still shared references.
const state = { nested: { n: 1 } };
const next = { ...state, nested: { ...state.nested, n: 2 } };
Common UI patterns
// immutable array update
function updateAt(arr, i, val) {
return [...arr.slice(0, i), val, ...arr.slice(i + 1)];
}
// props forwarding
function Button({ className, ...rest }) {
return <button className={cx('btn', className)} {...rest} />;
}
Footguns
| Issue | Detail |
|---|---|
| Shallow copy | Nested mutability leaks |
| Huge spreads | Math.max(...giantArray) can hit arg limits |
| Null spread | {...null} / {...undefined} OK (no-op); array spread needs iterable |
| Order | Rest params/properties last |
// TypeError
// [...null];
Interview answer (out loud)
“Rest gathers remaining args or properties into an array/object; spread expands iterables or own enumerable props into literals or calls. Object and array spreads are shallow copies. Rest parameters replace most arguments use cases and must be last.”
Spread and prototype
const proto = { hidden: true };
const obj = Object.create(proto);
obj.own = 1;
const copy = { ...obj };
copy.own; // 1
copy.hidden; // undefined — spread only own enumerable props
Same rule as Object.assign. Inherited methods won’t copy via spread.
Arrays: sparse holes
const sparse = [1, , 3];
[...sparse]; // [1, undefined, 3] — holes become undefined
Know this when cloning sparse structures from new Array(n).
Function apply replacement
// old
fn.apply(null, args);
// new
fn(...args);
Both hit argument length limits on enormous arrays; for huge numeric data prefer typed arrays and loops, not spread into calls.
Further reading
Related
- Destructuring Objects and Arrays
- Deep vs Shallow Copy
- Immutability Patterns in JS
- Default Parameters
Related guides
- AbortControllerCancel fetch and other async work with AbortController and AbortSignal — timeouts, race conditions, and cleanup when components unmount.
- Array find, some, every, includesShort-circuiting array predicates: find, findIndex, some, every, and includes — when to use each and common interview traps.
- Array map, filter, reducemap, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.
- Arrow Functions Deep DiveLexical this, no arguments object, no construct, concise bodies — when arrows help and when methods and generators need classic functions.
- Async Iteration and for await...ofAsync iterables, for await...of, and streaming data — how async iterators differ from Promise.all and when to use each.