skip to content

Why does ['1', '7', '11'].map(parseInt) return [1, NaN, 3] instead of [1, 7, 11]?

level: middleimportance: should knowfreq 55%

answer

  1. count the arguments map passes
  2. the callee takes a second parameter
  3. index lands where radix belongs
  4. base one is not legal
  5. arrow wrapper pins the arity

basics

~20 s

Array.prototype.map calls its callback with three arguments — element, index and array — so the index is passed as parseInt's radix parameter. The calls become parseInt('1', 0), parseInt('7', 1) and parseInt('11', 2), giving 1, NaN and 3.

solid answer

~40 s

`map` always invokes the callback as `callback(element, index, array)`, and `parseInt(string, radix)` accepts a second parameter. Passing `parseInt` point-free therefore silently wires the index into the radix slot. Index 0 means "no radix given", so `parseInt('1', 0)` uses base 10 and yields `1`. Radix `1` is outside the legal range of 2–36, so `parseInt('7', 1)` is `NaN`. Radix `2` is binary, and `'11'` in binary is `3`. The fix is to control the arity yourself: `map(s => parseInt(s, 10))`, or `map(Number)` since `Number` ignores extra arguments. The general lesson is that point-free style is only safe when the function you pass has exactly the arity you intend — any optional second parameter will quietly receive the index.

code

javascript · 6 lines
javascript
console.log(['1', '7', '11'].map(parseInt));       // [1, NaN, 3]
console.log(['1', '7', '11'].map(s => parseInt(s, 10))); // [1, 7, 11]
console.log(['1', '7', '11'].map(Number));         // [1, 7, 11]

// why each element behaves as it does
console.log(parseInt('1', 0), parseInt('7', 1), parseInt('11', 2)); // 1 NaN 3

go deeper

for a junior

Recall that map hands the callback the element, its index and the array, and that passing a built-in function directly can therefore receive arguments you did not intend. Know the safe form: map(s => parseInt(s, 10)).

for a middle

Walk the three calls out loud and explain each result: radix 0 means unspecified so base 10, radix 1 is outside the legal 2 to 36 range so NaN, and radix 2 reads '11' as binary 3.

for a senior

Generalise the trap to any point-free callback whose callee has optional parameters, and describe how you catch it in review or with a lint rule before it reaches production data.

for a principal

Decide the codebase stance on point-free callbacks: where terseness is worth the arity hazard, what the lint configuration enforces, and how conversion helpers are standardised so parsing behaviour is consistent across teams.

## The two facts that collide This puzzle is famous because each half is unremarkable on its own; only together do they misbehave. **Fact one — `map` passes three arguments.** `Array.prototype.map` invokes the callback as `callback(element, index, array)`, always, whether or not the callback declares that many parameters. The same is true of `filter`, `flatMap`, `find`, `some` and friends. **Fact two — `parseInt` takes two parameters.** Its signature is `parseInt(string, radix)`, where `radix` is the base to interpret the digits in and must be an integer from 2 to 36. Writing `map(parseInt)` hands `map` a function whose second parameter is the radix, so the loop index lands there. ## Walking the three calls ```js ['1', '7', '11'].map(parseInt); // call 0: parseInt('1', 0, arr) // call 1: parseInt('7', 1, arr) // call 2: parseInt('11', 2, arr) // → [1, NaN, 3] ``` - `parseInt('1', 0)` — a radix of `0` (or `undefined`) means "unspecified", so `parseInt` infers the base: strings starting `0x` or `0X` are hex, everything else decimal. `'1'` is decimal `1`. - `parseInt('7', 1)` — base 1 is outside the legal 2–36 range, so `parseInt` returns `NaN` immediately without looking at the digits. - `parseInt('11', 2)` — binary `11` is `3`. The third argument (the array itself) is simply ignored, because `parseInt` declares only two parameters. Note that `parseInt` also stops at the first character that is not a valid digit for the radix, returning what it parsed so far, or `NaN` if nothing valid was parsed. That is why `parseInt('3', 2)` is `NaN` — `3` is not a binary digit. ## The fixes ```js ['1', '7', '11'].map(s => parseInt(s, 10)); // [1, 7, 11] — explicit radix, arity pinned ['1', '7', '11'].map(Number); // [1, 7, 11] — Number ignores extra arguments ['1.5', '2.5'].map(parseFloat); // [1.5, 2.5] — parseFloat takes one parameter ``` Wrapping in an arrow is the direct fix: the arrow has arity one, so the index never reaches `parseInt`, and you get to state the radix explicitly, which is good practice regardless. `map(Number)` works because `Number(value)` is a one-parameter conversion and ignores anything extra. It is not identical to `parseInt` though: `Number` requires the *whole* string to be a valid numeric literal (`Number('12px')` is `NaN` while `parseInt('12px', 10)` is `12`), and `Number('')` is `0` while `parseInt('', 10)` is `NaN`. Choose based on whether trailing junk should be tolerated. `map(parseFloat)` is safe for the opposite reason: `parseFloat` declares a single parameter, so there is no slot for the index to fall into. ## The general rule The bug is not really about `parseInt`; it is about **point-free style plus optional parameters**. Any time you pass an existing function directly to `map`, `filter` or `flatMap`, ask how many parameters that function actually accepts. If it accepts more than one, the index — and possibly the array — will be supplied whether you want them or not. ```js // safe: arity 1 ['a','b'].map(String); // think twice: the callee has optional extra parameters values.map(myHelper); // does myHelper(value, index, array) still mean what you want? ``` The defensive habit is to write the explicit arrow whenever the callee is not a function you wrote with exactly one parameter. It costs seven characters and removes an entire class of silent wrong-answer bugs. ## What interviewers are testing They want to hear you name the mechanism — the callback arity contract of `map` — rather than recite the output. A candidate who says "the index becomes the radix" and then explains why base 1 is `NaN` has demonstrated they can reason about it in an unfamiliar case, which is the whole point.

  • Why is ['1','7','11'].map(Number) safe when map(parseInt) is not?
    `Number(value)` declares a single parameter and ignores anything else it is handed, so the index has nowhere to land. `parseInt` declares `(string, radix)`, so the index becomes the base. Be aware the two conversions differ: `Number('12px')` is `NaN` whereas `parseInt('12px', 10)` is `12`, and `Number('')` is `0` whereas `parseInt('', 10)` is `NaN`.
  • Which other array methods pass the index into the callback the same way?
    The whole callback family does: `map`, `filter`, `flatMap`, `forEach`, `find`, `findIndex`, `some` and `every` all call back with `(element, index, array)`, and `reduce` uses `(accumulator, element, index, array)`. So the same point-free hazard applies anywhere you hand one of them a function that accepts more than one parameter.
  • How would you spot this class of bug in review rather than in production?
    Look for a bare function reference passed to an array method and check its declared arity. Any callee with optional parameters — a radix, a depth, an options object, a default flag — is a hazard. The team-level fix is a lint rule such as the unicorn `no-array-callback-reference` rule, or a convention of always writing an explicit arrow.

saying these in an interview costs you the question

  • Says parseInt is broken or buggy
  • Thinks map passes only the element to the callback
  • Claims radix 1 means base 10
  • Assumes the array argument causes the failure
  • Says wrapping in an arrow changes parseInt's behaviour

context