Bitsy Explains
Home/Cursed JavaScript/Lesson 8

Why map(parseInt) Gives NaN

Why ["1", "2", "3"].map(parseInt) gives [1, NaN, NaN], what map really passes to your function, and the safe way to turn text into numbers.

Lesson 8 of 103 min readVideo
Video thumbnail: Why map(parseInt) Gives NaN Watch the 90-second video
Explain like I'm five

parseInt reads text and pulls out a whole number. map is a helper that hands your function each item, but it also tells it the item's position in the line, counting from zero. parseInt hears that position and thinks it is being told which number system to read in. Number ignores the position, so it never gets confused.

The curse

console.log(["1", "2", "3"].map(parseInt)); // prints: [ 1, NaN, NaN ]

You would expect [1, 2, 3]. Instead you get one real number and two broken ones.

What map really hands your function

map calls your function with three arguments every time. It passes the item, its position (the index), and the whole array. Most functions ignore the extras. parseInt does not, because its second parameter is the radix, the number system to read in. 10 means normal numbers, 2 means binary, 16 means hexadecimal.

So map(parseInt) really runs these three calls.

console.log(parseInt("1", 0)); // prints: 1
console.log(parseInt("2", 1)); // prints: NaN
console.log(parseInt("3", 2)); // prints: NaN
  • A radix of 0 is treated as "not given", so "1" is read as a normal number.
  • A radix of 1 is not allowed (the valid range is 2 to 36), so you get NaN.
  • Radix 2 is binary, which only has the digits 0 and 1. "3" has no valid digit, so you get NaN.

How to break the curse

console.log(["1", "2", "3"].map(Number)); // prints: [ 1, 2, 3 ]
console.log(["1", "2", "3"].map((s) => parseInt(s, 10))); // prints: [ 1, 2, 3 ]

Number only looks at its first argument, so the index can't confuse it. If you do need parseInt, wrap it in a small arrow function that passes only the value, plus 10 for normal numbers.

The two behave differently on messy text. Number("12px") is NaN and Number("") is 0, while parseInt("12px", 10) is 12 and parseInt("", 10) is NaN. Pick the one that matches what you want to accept.

The same trap waits for any function that has optional extra parameters. Handing a function straight to map is only safe when you know it ignores the extra arguments.

Bonus twist: parseInt(0.0000005)

console.log(parseInt(0.0000005)); // prints: 5
console.log(String(0.0000005)); // prints: 5e-7

parseInt works on text, so it turns the number into a string first. Very small numbers are written in exponent shorthand, "5e-7", which means 5 times ten to the power of minus 7. parseInt reads the 5, stops at the e because it is not a digit, and returns 5.

To cut the decimals off a number, use Math.trunc or Math.floor. Keep parseInt for reading text.

Rule of thumb

Don't hand parseInt straight to map, use .map(Number) instead.

Common mistake

Calling parseInt(text) without a radix. It mostly works, but text that starts with 0x is read as hexadecimal. Write parseInt(text, 10) so the result never depends on how the text starts.

Check yourself

Why does ["1", "2", "3"].map(parseInt) give NaN for "2"?