skip to content

In JavaScript, what does a ...rest parameter give you that the legacy arguments object does not?

level: middleimportance: must knowfreq 70%

answer

  1. one is a real array
  2. the other needs converting first
  3. named parameters excluded vs included
  4. arrow functions have none of it
  5. rest must be last, single, no default

basics

~20 s

A rest parameter binds a real Array, so map, filter and reduce work directly, and it holds only the arguments beyond the named ones. The arguments object is array-like, holds every argument, is not available in arrow functions, and needs conversion before array methods work.

solid answer

~50 s

`function f(a, ...rest)` binds `rest` to a genuine `Array` containing only the arguments after the named ones, so `rest.map(...)` and `rest.length` work immediately and the signature documents that the function is variadic. The `arguments` object is a different thing: it exists in every non-arrow function, always holds *all* passed arguments including the named ones, and is array-like — it has `length` and index properties and is iterable, but no `Array.prototype` methods, so you need `[...arguments]` or `Array.from(arguments)` first. Arrow functions have no `arguments` binding at all. There are also constraints on rest: it must be the last parameter, there can be only one, and it cannot carry a default. In modern code rest is the default choice; `arguments` mainly survives in old code and in the odd case where you want the full argument list without naming anything.

code

javascript · 10 lines
javascript
function withRest(first, ...rest) {
  return { first, rest, isArray: Array.isArray(rest) };
}

function withArguments(first) {
  return { count: arguments.length, asArray: Array.from(arguments) };
}

console.log(withRest(1, 2, 3));      // { first: 1, rest: [2, 3], isArray: true }
console.log(withArguments(1, 2, 3)); // { count: 3, asArray: [1, 2, 3] }

go deeper

for a junior

Know the syntax and the headline difference: ...rest gives you an array you can call array methods on, while arguments needs Array.from or spread first. Remember rest goes last in the signature.

for a middle

Explain the mechanics an interviewer probes: rest excludes the named parameters, arguments includes everything, arrows have no arguments binding, and the syntax rules (last, single, no default). Knowing the mapped-vs-unmapped behaviour separates you here.

for a senior

Talk about migrating legacy variadic code: replacing arguments with rest changes what a wrapper forwards, and in sloppy mode it silently removes the parameter aliasing that old code may have depended on. Be ready to say how you would verify such a refactor with tests.

for a principal

Own the API stance — variadic signatures are convenient but hostile to evolution, since you cannot add a trailing parameter later. Argue when a function should take an explicit array or options object instead, and set the codebase convention for wrappers that must forward arbitrary arguments.

## Two ways to reach a variable argument list JavaScript lets you call any function with any number of arguments. Two mechanisms let the callee see the extras. ```js function sumRest(...nums) { return nums.reduce((a, b) => a + b, 0); } function sumArgs() { return Array.from(arguments).reduce((a, b) => a + b, 0); } ``` Both return `6` for `(1, 2, 3)`. The differences are what interviews are after. ## Rest is an ordinary Array `nums` above is created by the engine as a real array — `Array.isArray(nums)` is `true`, and every `Array.prototype` method is available with no conversion step. When no extra arguments are passed it is an empty array, never `undefined`, so `nums.length === 0` is safe. It also *starts after the named parameters*: ```js function tag(label, ...values) { return `${label}: ${values.join(', ')}`; } tag('ids', 1, 2, 3); // 'ids: 1, 2, 3' — values is [1, 2, 3] ``` The syntax has three hard rules: rest must be the **last** parameter (`function f(...a, b)` is a SyntaxError), there can be only **one**, and it **cannot have a default** (`function f(...a = [])` is a SyntaxError). A trailing comma after it is also a SyntaxError. ## arguments is array-like, not an array `arguments` is an object the engine creates for every non-arrow function invocation. It has numeric index properties and a `length`, and since ES2015 it also has `Symbol.iterator`, so spreading and `for...of` work on it. What it does not have is `Array.prototype`, so this fails: ```js function broken() { return arguments.map(String); // TypeError: arguments.map is not a function } ``` The conversions are `[...arguments]` or `Array.from(arguments)`. `arguments` also always contains **all** arguments, so with `function f(a, ...rest)` a call `f(1, 2, 3)` gives `rest` of `[2, 3]` but `arguments` of length 3. `arguments.callee` — a reference to the running function — exists in sloppy mode but throws a `TypeError` when accessed in strict mode, and modules and class bodies are always strict, so it is effectively dead. ## Arrow functions have no arguments binding An arrow function does not create its own `arguments`. A reference inside one resolves lexically to the enclosing ordinary function's object, or throws a `ReferenceError` at the top level of a module: ```js const f = (...args) => args.length; // works: rest parameters are fine in arrows const g = () => arguments.length; // resolves to an outer function's arguments, or throws ``` This alone makes rest the only workable option in arrow-heavy code. ## The mapping trap in sloppy mode In non-strict code, when a function's parameter list is *simple* — no defaults, no rest, no destructuring — `arguments` is **mapped** to the named parameters: writing one changes the other. ```js function mapped(a) { arguments[0] = 99; return a; // 99 in sloppy mode } ``` As soon as the parameter list is non-simple, or the code is strict, the link is gone and `arguments` is a plain snapshot of the passed values. That inconsistency — the same expression meaning two different things depending on strictness and on whether any parameter has a default — is a real source of confusion in legacy code and another reason the language moved on. ## Related restriction worth knowing A function whose parameter list is non-simple cannot carry a `'use strict'` directive in its body; it is a SyntaxError: ```js function f(a = 1) { 'use strict'; // SyntaxError } ``` The fix is to make the surrounding scope strict — which modules and classes already are. ## Rest versus spread — same token, opposite directions `...` in a *parameter list* collects: it packs the remaining arguments into an array. `...` at a *call site* or inside an array or object literal expands: it unpacks an iterable into individual values. `function f(...a)` and `f(...arr)` are mirror images, and mixing up which is which is a classic stumble under interview pressure. ## What to say Lead with "rest is a real array, `arguments` is array-like", add that rest excludes the named parameters while `arguments` includes everything, note the arrow-function gap, and mention the syntax constraints on rest. If you can also describe the sloppy-mode mapping, you are demonstrating that you know why the old mechanism was replaced rather than just that it was.

  • Why does adding a default to any parameter change how arguments behaves in non-strict code?
    Because the mapping between `arguments` indices and named parameters only exists for a *simple* parameter list. Any default, rest parameter or destructuring pattern makes the list non-simple, and the spec then creates an unmapped arguments object — a plain snapshot. So `arguments[0] = 99` stops writing through to the parameter. Strict-mode functions are always unmapped regardless of the parameter list.
  • Can you use a rest parameter and still know how many arguments the caller actually passed?
    Yes — `rest.length` plus the number of named parameters the caller filled, or `arguments.length` in a non-arrow function, which counts what was actually passed. Note the named parameters are not in `rest`, so `f(a, ...rest)` called as `f(1)` gives `rest.length === 0` while `arguments.length === 1`. If you need the true count often, that is usually a hint the signature should take an explicit array instead.
  • Is there anything arguments can still do that a rest parameter cannot?
    Very little that matters. It gives you the full argument list without declaring anything, which is occasionally handy when patching or wrapping a function whose signature you do not control, and in sloppy mode it aliases the named parameters. Both are niche; `arguments.callee` is unusable in strict code, and arrow functions have no `arguments` at all, so new code should use rest.

saying these in an interview costs you the question

  • Calls arguments a real array with map and filter
  • Thinks rest includes the named parameters too
  • Believes arrow functions have their own arguments object
  • Puts the rest parameter before other parameters
  • Gives a rest parameter a default value
  • Says arguments is not iterable so spread cannot convert it

context