What does a JavaScript function's `length` property report, and how do default and rest parameters change it?
answer
- declared, not passed
- counting stops early
- everything after the stop is invisible
- a whole pattern counts once
- non-writable but configurable
basics
~20 sA function's length is its declared arity: the number of parameters before the first one with a default value and before any rest parameter. Defaults and rest contribute nothing, and a destructuring pattern counts as one parameter.
solid answer
~40 s`fn.length` is the count of parameters declared *up to* the first parameter that has a default or is a rest parameter — everything from that point on is excluded, even ordinary parameters after it. So `(function (a, b) {}).length` is 2, `(function (a, b = 1, c) {}).length` is 1, and `((...args) => {}).length` is 0. A destructuring pattern counts as a single parameter, so `function f({ a, b }) {}` also has length 1. It is a per-function own property that is non-writable but configurable, so libraries can override it with `Object.defineProperty`. The important contrast is with `arguments.length`, which is how many arguments the *caller* actually passed at that call — `fn.length` is fixed by the declaration and says nothing about any particular call.
code
javascript · 6 linesconsole.log((function (a, b, c) {}).length); // 3
console.log((function (a, b = 1, c) {}).length); // 1
console.log((function (a, ...rest) {}).length); // 1
console.log(((...args) => {}).length); // 0
console.log((function ({ a, b, c }) {}).length); // 1
console.log((function ({ a = 1 } = {}) {}).length); // 0go deeper
Know that fn.length is the declared parameter count and that it is not the number of arguments a call passed — that is arguments.length.
State the stopping rule precisely: counting ends at the first parameter with a default or the rest parameter, so later plain parameters are excluded too, and a destructuring pattern counts as one.
Point at the practical trap — adding a default to an existing parameter silently changes reported arity, which can flip a library's calling-convention branch with no error. Know that wrappers redefine length via Object.defineProperty to preserve it.
Treat arity-based dispatch as a fragile contract: it couples callers to a signature detail that a routine refactor can change. Argue for explicit configuration or named options over introspecting parameter counts in any API you own.
## Declared arity, not call arity Every function object carries an own `length` property giving its *declared arity*. It is decided by the source text of the parameter list and never changes per call. ```js (function (a, b, c) {}).length; // 3 ``` That is the whole story for a simple parameter list. The interesting cases are the ones the specification deliberately excludes. ## Counting stops at the first default or rest The count is the number of parameters *before* the first one that has an initialiser or is a rest parameter. Everything from that point onward is excluded, including plain parameters that come after: ```js (function (a, b = 1, c) {}).length; // 1 — counting stopped at b (function (a, ...rest) {}).length; // 1 (function (...args) {}).length; // 0 (function (a = 1, b = 2) {}).length; // 0 ``` The `(a, b = 1, c)` case is the one people get wrong: the intuitive guess is 2 (the two parameters without defaults), but the rule is positional — once counting stops, it stops. ## Destructuring patterns count as one A pattern in the parameter list is a single parameter, however many bindings it produces: ```js (function ({ a, b, c }) {}).length; // 1 (function ([x, y], z) {}).length; // 2 (function ({ a = 1 } = {}) {}).length; // 0 — the parameter has a default ``` That last line is why the options-object idiom always reports zero arity, which is worth remembering if anything in your stack inspects `length`. ## fn.length versus arguments.length They answer different questions and are the classic pairing in an interview: ```js function probe(a, b) { return { declared: probe.length, passed: arguments.length }; } probe(1); // { declared: 2, passed: 1 } probe(1, 2, 3, 4); // { declared: 2, passed: 4 } ``` `fn.length` is a property of the function; `arguments.length` is a property of one invocation. Neither constrains the other — JavaScript never rejects a call for passing too few or too many arguments. ## The property's own attributes `length` is an own, non-enumerable, non-writable but **configurable** property of each function. Non-writable means plain assignment silently does nothing in sloppy mode and throws in strict mode; configurable means it can be redefined: ```js function wrap(fn) { const wrapped = (...args) => fn(...args); Object.defineProperty(wrapped, 'length', { value: fn.length }); return wrapped; } wrap(function (a, b) {}).length; // 2 instead of 0 ``` That is exactly what wrapper utilities do to keep a proxy function's reported arity meaningful, since a `(...args)` wrapper would otherwise report `0` and lose the information. ## Where arity is actually used The honest answer is: in libraries, not in application code. Middleware and hook systems have historically branched on how many parameters a callback declares to decide which calling convention to use — a callback declaring one extra parameter is treated as "wants a completion callback" — and generic wrappers preserve `length` so those checks keep working through a layer of indirection. That is precisely why the exclusion rules bite: adding a default to an existing parameter silently drops the reported arity and can change which branch a library takes, with no error and no obvious cause. It is a small but genuinely surprising failure mode. ## What to say Define it as declared arity, state the stopping rule with the `(a, b = 1, c)` example, note that a pattern counts as one, and contrast it with `arguments.length`. Mentioning that it is configurable — and that wrappers redefine it — shows you have met it in practice rather than only read about it.
- Why does `(function (a, b = 1, c) {}).length` report 1 rather than 2?Because the rule is positional, not a filter. The count is the number of parameters before the first one carrying an initialiser, and everything from that parameter onward — including plain parameters declared after it — is excluded. Counting stopped at `b`, so only `a` was counted. The same happens with a rest parameter, which ends the count wherever it appears.
- Can you change a function's reported length?Not by assignment — the property is non-writable, so `fn.length = 3` is silently ignored in sloppy mode and throws in strict mode. It is configurable, so `Object.defineProperty(fn, 'length', { value: 3 })` works. Wrapper utilities do exactly that so a `(...args)` proxy keeps the wrapped function's arity instead of reporting zero.
- Does JavaScript enforce fn.length at call time in any way?No. Calls are never rejected for argument count: extra arguments are ignored by the named parameters and still appear in `arguments`, and missing ones are `undefined`. `fn.length` is purely introspective metadata that library code may choose to read; the runtime itself never consults it when dispatching a call.
saying these in an interview costs you the question
- Says length counts every declared parameter
- Thinks length reports how many arguments were passed
- Counts parameters declared after one with a default
- Expects a destructuring pattern to count each binding
- Claims fn.length = 3 changes the reported arity