What does `['1', '7', '11'].map(parseInt)` return, and what does that result reveal about how a higher-order function calls the callback you hand it?
answer
- prediction [1, 7, 11] is wrong
- callback gets element, index, array
- parseInt's second parameter is radix
- radix 1 illegal, radix 2 reads binary
- wrap to fix arity
basics
~20 sIt returns [1, NaN, 3]. The callback is invoked with three arguments — element, index and array — so parseInt receives the index as its radix: radix 1 is invalid and radix 2 reads "11" as binary 3. Wrap the callback to control its arity.
solid answer
~40 sThe result is `[1, NaN, 3]`. `map` calls its callback with `(element, index, array)`, and `parseInt(string, radix)` happens to accept a second parameter, so the index arrives as the radix: `parseInt('1', 0)` treats radix 0 as unspecified and gives 1, `parseInt('7', 1)` is an invalid radix and gives `NaN`, and `parseInt('11', 2)` reads binary and gives 3. The general lesson is that when you pass a bare function reference as a callback you silently opt into the higher-order function's full argument contract, including arguments you never intended to receive. The fix is to control arity yourself: `.map((s) => parseInt(s, 10))`, or `.map(Number)` if plain numeric conversion is what you want. Any callback target with optional extra parameters is a candidate for this bug.
code
javascript · 13 linesconsole.log(['1', '7', '11'].map(parseInt)); // [1, NaN, 3]
// what actually runs
console.log(parseInt('1', 0)); // 1 (radix 0 -> unspecified)
console.log(parseInt('7', 1)); // NaN (radix 1 is illegal)
console.log(parseInt('11', 2)); // 3 (binary)
// fixes: pin the arity yourself
console.log(['1', '7', '11'].map((s) => parseInt(s, 10))); // [1, 7, 11]
console.log(['1', '7', '11'].map(Number)); // [1, 7, 11]
const unary = (fn) => (x) => fn(x);
console.log(['1', '7', '11'].map(unary(parseInt))); // [1, 7, 11]go deeper
Know that array callbacks receive the index as a second argument, and that passing a bare function reference exposes it. Remember the safe habit: write (s) => parseInt(s, 10) rather than parseInt.
Explain the exact three calls that occur and why radix 0, 1 and 2 produce 1, NaN and 3. An interviewer expects you to generalise it to any callback with optional parameters, not to memorise this one example.
Show how you would catch this in real code: which inputs hide it, why a wrong number is more dangerous than a NaN, and how you would review point-free callbacks in a codebase for the same arity mismatch.
Own the API-design side: a higher-order function's callback signature is a public contract, so adding an argument to it is a breaking change for every point-free caller. Be ready to argue for narrow callback signatures for exactly that reason.
## The output ```js console.log(['1', '7', '11'].map(parseInt)); // [1, NaN, 3] ``` Most people predict `[1, 7, 11]`. The gap between prediction and reality is exactly why the question gets asked. ## Why the callback receives more than you expected When you pass a function to a higher-order function, the *receiver* decides how it will be invoked. `Array.prototype.map` documents its callback as being called with three arguments: the current element, its index, and the array being traversed. Nothing forces a callback to declare all three; JavaScript quietly discards extra arguments when a function declares fewer parameters. That silence is usually convenient — `.map((x) => x * 2)` ignores the index without ceremony — and occasionally lethal, when the callback you passed *does* declare a second parameter that means something entirely different. `parseInt` has the signature `parseInt(string, radix)`. So the three calls that actually happen are: ```js parseInt('1', 0, arr); // 1 radix 0 -> treated as unspecified, so base 10 parseInt('7', 1, arr); // NaN radix 1 is outside the legal range 2..36 parseInt('11', 2, arr); // 3 binary 11 is decimal 3 ``` The third argument (the array) is simply ignored — `parseInt` declares only two parameters. Note the details worth stating out loud: a radix of `0` (or `undefined`) means "guess", which is base 10 for a plain decimal-looking string; legal radices are 2 through 36, so 1 is rejected with `NaN`; and `'11'` in base 2 is a perfectly valid parse producing 3, which is why the last element is a wrong number rather than `NaN`. A silently wrong number is worse than a `NaN`, because nothing downstream flags it. ## The general rule Passing a bare function reference — `map(parseInt)`, `forEach(this.handle)`, `then(console.log)` — is *point-free* style, and it is only safe when the callback's parameter list is compatible with everything the caller will pass. Ask two questions before you do it: 1. How many arguments does this higher-order function supply? 2. Does my callback declare optional parameters that would absorb them with a different meaning? If the answer to (2) is yes, wrap it. The wrapper is the arity adapter: ```js ['1', '7', '11'].map((s) => parseInt(s, 10)); // [1, 7, 11] ['1', '7', '11'].map(Number); // [1, 7, 11] ``` `Number` is safe here because `Number(value)` ignores any extra arguments — it declares one parameter. That difference, not luck, is what makes one point-free callback fine and the other broken. (`Number` and `parseInt` are not interchangeable in general: `Number('12px')` is `NaN` while `parseInt('12px', 10)` is 12, and `Number('')` is 0 while `parseInt('', 10)` is `NaN`. Pick on parsing semantics, not on which one dodges this bug.) If you want a reusable adapter, a tiny higher-order helper does it: ```js const unary = (fn) => (x) => fn(x); ['1', '7', '11'].map(unary(parseInt)); // [1, 7, 11] ``` `unary` is itself a higher-order function: it takes a function and returns a function that forwards exactly one argument. ## The same trap elsewhere This is not a `map` quirk. Any higher-order function that supplies more arguments than the obvious one can trigger it. Array traversal and predicate methods pass `(element, index, array)`. Reduction passes an accumulator first. Callback-style APIs frequently pass an error or a status argument you did not plan for. The failure mode is identical: your callback has an optional parameter with a different meaning, the caller fills it, and you get a plausible wrong answer rather than an exception. ## Diagnosing it in the wild The symptom is a transformation that is right for some inputs and wrong for others — here, index 0 works, so a one-element test array passes and hides the bug entirely. When a callback misbehaves only for later elements, suspect the index. Logging the callback's `arguments` length, or temporarily wrapping the callback in `(...args) => { console.log(args); return original(...args); }`, exposes the real call shape in seconds.
- Why is `.map(Number)` safe when `.map(parseInt)` is not?`Number` declares a single parameter and ignores anything beyond it, so the index and array arrive and are discarded. `parseInt` declares a second parameter, `radix`, which the index happily fills. The rule is about the callback's declared parameters, not about the two functions being similar — and they are not interchangeable: `Number('12px')` is `NaN` while `parseInt('12px', 10)` is 12.
- How would you write a helper that makes any function safe to pass point-free?A tiny higher-order adapter: `const unary = (fn) => (x) => fn(x);`. It takes a function and returns one that forwards exactly one argument, so extra arguments from the caller are dropped before they reach the target. The same idea generalises to `arity(n, fn)` if you need to keep two arguments.
- Why does the bug survive a quick unit test?Because index 0 behaves correctly — `parseInt('1', 0)` is 1 — so a single-element or all-index-0 test passes. The wrong values appear only from index 1 onward, and `parseInt('11', 2)` returns a plausible number rather than `NaN`. Test data with several elements and non-decimal-looking strings exposes it.
saying these in an interview costs you the question
- Answers [1, 7, 11] and stops
- Says map passes only the element to the callback
- Claims the result is NaN for every element
- Thinks .map(parseInt, 10) sets the radix
- Calls it a parseInt bug rather than an arity mismatch