Implement numberOfArguments(...) — a function that returns how many arguments were actually handed to it at the moment it was called, regardless of how its parameters were declared. This is the call-time argument count, and it is a different thing from a function's declared arity (the number of named parameters you wrote, available as fn.length). A function can declare three parameters and be called with none, or declare none and be called with ten — this question is about counting what came in, not what was named.
// Accepts any number of arguments of any type.
// Returns the count of arguments passed at the call site.
function numberOfArguments(...args: unknown[]): number;
numberOfArguments(); // → 0
numberOfArguments(1, 2, 3); // → 3
numberOfArguments('a'); // → 1
// A passed undefined still counts — it filled an argument slot.
numberOfArguments(undefined); // → 1
numberOfArguments(null); // → 1
numberOfArguments(...[1, 2, 3]); // → 3 (spread fills three slots)
undefined counts. numberOfArguments(undefined) passes one argument that happens to be undefined. The result is 1, not 0 — the slot was filled.null counts too. Same reasoning: null is a value that occupies an argument slot.numberOfArguments(...[1, 2, 3]) is identical to numberOfArguments(1, 2, 3) — three arguments.fn.length. fn.length reports declared arity fixed at definition time; this question asks for the count at call time, which varies per call.You'll write a function that ignores its own parameter list entirely and just reports how many values the caller actually passed.
Think of a function call like seats on a bus. When you write numberOfArguments(1, 2, 3), three passengers board — and we want to count passengers, not how many seats the driver labelled in advance. The number of named parameters a function declares (its arity) is fixed when you write the function. The number of arguments passed is decided fresh on every call. Those two numbers are unrelated: the same function can be called with zero arguments one moment and ten the next. We want the second number — the live, call-time count.
Picture one function that declares no named parameters at all. Each time it's called, the caller drops some values into the call's argument slots, and our job is to report how many slots got filled. An explicitly passed undefined or null still fills a slot — passing a value, even an empty-looking one, is different from passing nothing. So numberOfArguments(undefined) is 1, not 0.
If you've seen the classic arguments object before, the instinct is to reach for it. arguments is an array-like object that older-style functions expose, holding everything that was passed. So you might write an arrow function and read arguments.length:
const numberOfArguments = () => arguments.length;
This looks plausible, but it's broken — and broken in a sneaky way. Arrow functions do not have their own arguments object. Unlike a regular function, an arrow doesn't bind arguments; it inherits whatever arguments means in the surrounding scope. At the top level of a module there is no arguments at all, so this throws a ReferenceError. Worse, if you'd defined this arrow inside another regular function, arguments would silently resolve to that outer function's arguments — so you'd get a number that has nothing to do with how numberOfArguments was called. Either way, the answer is wrong.
// A rest parameter (...args) gathers every argument passed at the call site
// into a real array named `args` — this works in arrow functions, where the
// `arguments` object is unavailable. The count we want is simply how many
// landed in that array.
const numberOfArguments = (...args) => args.length;
module.exports = { numberOfArguments };
The fix is the rest parameter ...args. Where the broken version borrowed an arguments object that arrow functions don't own, the rest parameter creates a genuine array, scoped to this call, holding exactly the values that were passed. Because it's a real array (not an array-like object), args.length is the count directly — no conversion needed. And note what ...args does to the declared arity: a rest parameter contributes 0 to fn.length, so numberOfArguments.length is 0 even though the function happily counts however many arguments you pass. That's the whole point made concrete — declared arity (0) and call-time count (0, 1, 3, …) are independent.
Trace numberOfArguments(1, 'two', true, null, { x: 1 }) end to end.
At the call site, five values are passed: 1, 'two', true, null, and { x: 1 }. The rest parameter ...args collects all of them into one array:
call: numberOfArguments(1, 'two', true, null, { x: 1 })
bind: args = [1, 'two', true, null, { x: 1 }]
↑ ↑ ↑ ↑ ↑
slot0 slot1 slot2 slot3 slot4
read: args.length → 5
return 5
Every value lands in args, including null — it's a real value passed into a slot, so it counts like any other. The function never inspects what the values are; it only asks how many there are. args.length is 5, and that's the answer.
arguments inside an arrow function. Arrows don't bind their own arguments, so () => arguments.length throws a ReferenceError at module scope or silently reads an outer function's arguments when nested. Fix: use a rest parameter (...args) => args.length, which works regardless of scope or function form.undefined doesn't count. numberOfArguments(undefined) is 1, not 0. Passing undefined still fills an argument slot — it's different from passing nothing. The rest parameter captures it as args = [undefined], length 1.fn.length is the number of parameters declared before the first default or rest parameter — fixed at definition. This question wants the count passed at the call site, which changes per call. With (...args), numberOfArguments.length is 0 while the call-time count can be anything.arguments.length in a regular function and calling it done. It does work in a regular function, but it ties you to that function form and gives you an array-like object, not an array. The rest parameter is the modern, form-independent tool — prefer it.fn.length. The mirror-image question: given a function, how many parameters did it declare? That's fn.length, and it stops counting at the first default-valued or rest parameter — ((a, b = 1) => {}).length is 1. Counting declarations and counting call-time arguments are two different jobs.arguments object. In regular (non-arrow) functions, arguments is an array-like object with a .length. It predates rest parameters. To treat it as an array you'd write Array.from(arguments) or [...arguments]. Modern code prefers rest parameters precisely because they sidestep the array-like quirks and work in arrows....args, you can forward them to another function with spread: other(...args). This pattern — gather with rest, forward with spread — is how wrappers, decorators, and bind-like helpers pass an unknown number of arguments straight through.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.
Implement numberOfArguments(...) — a function that returns how many arguments were actually handed to it at the moment it was called, regardless of how its parameters were declared. This is the call-time argument count, and it is a different thing from a function's declared arity (the number of named parameters you wrote, available as fn.length). A function can declare three parameters and be called with none, or declare none and be called with ten — this question is about counting what came in, not what was named.
// Accepts any number of arguments of any type.
// Returns the count of arguments passed at the call site.
function numberOfArguments(...args: unknown[]): number;
numberOfArguments(); // → 0
numberOfArguments(1, 2, 3); // → 3
numberOfArguments('a'); // → 1
// A passed undefined still counts — it filled an argument slot.
numberOfArguments(undefined); // → 1
numberOfArguments(null); // → 1
numberOfArguments(...[1, 2, 3]); // → 3 (spread fills three slots)
undefined counts. numberOfArguments(undefined) passes one argument that happens to be undefined. The result is 1, not 0 — the slot was filled.null counts too. Same reasoning: null is a value that occupies an argument slot.numberOfArguments(...[1, 2, 3]) is identical to numberOfArguments(1, 2, 3) — three arguments.fn.length. fn.length reports declared arity fixed at definition time; this question asks for the count at call time, which varies per call.You'll write a function that ignores its own parameter list entirely and just reports how many values the caller actually passed.
Think of a function call like seats on a bus. When you write numberOfArguments(1, 2, 3), three passengers board — and we want to count passengers, not how many seats the driver labelled in advance. The number of named parameters a function declares (its arity) is fixed when you write the function. The number of arguments passed is decided fresh on every call. Those two numbers are unrelated: the same function can be called with zero arguments one moment and ten the next. We want the second number — the live, call-time count.
Picture one function that declares no named parameters at all. Each time it's called, the caller drops some values into the call's argument slots, and our job is to report how many slots got filled. An explicitly passed undefined or null still fills a slot — passing a value, even an empty-looking one, is different from passing nothing. So numberOfArguments(undefined) is 1, not 0.
If you've seen the classic arguments object before, the instinct is to reach for it. arguments is an array-like object that older-style functions expose, holding everything that was passed. So you might write an arrow function and read arguments.length:
const numberOfArguments = () => arguments.length;
This looks plausible, but it's broken — and broken in a sneaky way. Arrow functions do not have their own arguments object. Unlike a regular function, an arrow doesn't bind arguments; it inherits whatever arguments means in the surrounding scope. At the top level of a module there is no arguments at all, so this throws a ReferenceError. Worse, if you'd defined this arrow inside another regular function, arguments would silently resolve to that outer function's arguments — so you'd get a number that has nothing to do with how numberOfArguments was called. Either way, the answer is wrong.
// A rest parameter (...args) gathers every argument passed at the call site
// into a real array named `args` — this works in arrow functions, where the
// `arguments` object is unavailable. The count we want is simply how many
// landed in that array.
const numberOfArguments = (...args) => args.length;
module.exports = { numberOfArguments };
The fix is the rest parameter ...args. Where the broken version borrowed an arguments object that arrow functions don't own, the rest parameter creates a genuine array, scoped to this call, holding exactly the values that were passed. Because it's a real array (not an array-like object), args.length is the count directly — no conversion needed. And note what ...args does to the declared arity: a rest parameter contributes 0 to fn.length, so numberOfArguments.length is 0 even though the function happily counts however many arguments you pass. That's the whole point made concrete — declared arity (0) and call-time count (0, 1, 3, …) are independent.
Trace numberOfArguments(1, 'two', true, null, { x: 1 }) end to end.
At the call site, five values are passed: 1, 'two', true, null, and { x: 1 }. The rest parameter ...args collects all of them into one array:
call: numberOfArguments(1, 'two', true, null, { x: 1 })
bind: args = [1, 'two', true, null, { x: 1 }]
↑ ↑ ↑ ↑ ↑
slot0 slot1 slot2 slot3 slot4
read: args.length → 5
return 5
Every value lands in args, including null — it's a real value passed into a slot, so it counts like any other. The function never inspects what the values are; it only asks how many there are. args.length is 5, and that's the answer.
arguments inside an arrow function. Arrows don't bind their own arguments, so () => arguments.length throws a ReferenceError at module scope or silently reads an outer function's arguments when nested. Fix: use a rest parameter (...args) => args.length, which works regardless of scope or function form.undefined doesn't count. numberOfArguments(undefined) is 1, not 0. Passing undefined still fills an argument slot — it's different from passing nothing. The rest parameter captures it as args = [undefined], length 1.fn.length is the number of parameters declared before the first default or rest parameter — fixed at definition. This question wants the count passed at the call site, which changes per call. With (...args), numberOfArguments.length is 0 while the call-time count can be anything.arguments.length in a regular function and calling it done. It does work in a regular function, but it ties you to that function form and gives you an array-like object, not an array. The rest parameter is the modern, form-independent tool — prefer it.fn.length. The mirror-image question: given a function, how many parameters did it declare? That's fn.length, and it stops counting at the first default-valued or rest parameter — ((a, b = 1) => {}).length is 1. Counting declarations and counting call-time arguments are two different jobs.arguments object. In regular (non-arrow) functions, arguments is an array-like object with a .length. It predates rest parameters. To treat it as an array you'd write Array.from(arguments) or [...arguments]. Modern code prefers rest parameters precisely because they sidestep the array-like quirks and work in arrows....args, you can forward them to another function with spread: other(...args). This pattern — gather with rest, forward with spread — is how wrappers, decorators, and bind-like helpers pass an unknown number of arguments straight through.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.