In JavaScript, what does a ...rest parameter give you that the legacy arguments object does not?
answer
- one is a real array
- the other needs converting first
- named parameters excluded vs included
- arrow functions have none of it
- rest must be last, single, no default
basics
~20 sA 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 linesfunction 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
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.
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.
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.
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