
Why ['1', '7', '11'].map(parseInt) returns [1, NaN, 3] in JavaScript - libria
https://medium.com/dailyjs/parseint-mystery-7c4368ef7b21
======
roryrjb
This isn't strange or surprising. parseInt takes two arguments, the second one
is the radix and map will call with three arguments, the value, the index and
the whole array. You just have to know this and it might be different in other
languages. [ ... ].map(x => ...) is the right way to do this.

~~~
sametmax
It may be logical but:

\- I know all that, have been programming for 15 years, yet I still would have
done the mistake.

\- Simple unit tests may very well not catch this bug the first time.

\- The language design allows your brain to ignore the index parameter because
JS accepts superfluous parameters, which is a terrible decision.

\- map() is a mapping primitive. It's supposed to adapt a type to another type
so that you can pass it to a monoid. JS breaks this convention.

\- Even traditionally not functional languages know better than that and
separate iteration from indexing. E.G: python map() just maps, and if you want
numbering, you use explicitly enumerate(). Ruby has each() and
each_with_index(), etc.

Just another of the numerous sucky things in JS. They are not big, but they
accumulate very quickly and make it one of the worse language existing.
Certainly the worst of all modern stack languages. It's a shame it has a
monopoly on the most awesome platform in the world: the web.

In fact, it's such a big problem the most popular JS projects are all things
to avoid coding in JS or workaround JS deficiencies: typescript, coffeescript,
jsx, babel, webpack, lowdash ...

~~~
Oreb
As a (mostly) outsider, I’ve never understood why JavaScript is so popular in
web dev circles. There are so many awesome compile-to-JS languages these days
(ClojureScript, PureScript, Elm, ReasonML, Scala.js, etc). What makes people
want to use JavaScript instead?

~~~
RussianCow
Any time you use anything non-native, you add layers of indirection and
potentially lots of impedance mismatch between languages/runtimes. For
instance, I know all of the ones you mentioned would be non-starters at my
current job because it means we would have to wrap all of our JS
libraries/APIs in the language of choice, which is a non-trivial amount of
work. Plus, debugging becomes more challenging as you're basically having to
do it at two levels simultaneously.

Even with all of JavaScript's deficiencies, it's still far easier to just deal
with them than to switch languages entirely.

------
shay_ker
This has less to do with "parseInt" than it has to do with "map" in
JavaScript. It doesn't work exactly like .map in Java, Ruby, Elixir, etc.,
because ".map" in JS also passes in the index, and the full array.

------
nivertech
Disclaimer: I don't know Javascript, but that's why:

    
    
      > ['1','7','11'].map(console.log)
      1 0 [ '1', '7', '11' ]
      7 1 [ '1', '7', '11' ]
      11 2 [ '1', '7', '11' ]
      [ undefined, undefined, undefined ]
      
      > parseInt(1,0)
      1
      > parseInt(7,1)
      NaN
      > parseInt(11,2)
      3
    

The correct way is:

    
    
      > ['1','7','11'].map(x => parseInt(x))
      [ 1, 7, 11 ]
    

same as:

    
    
      > ['1','7','11'].map(x => parseInt(x, 10))
      [ 1, 7, 11 ]

~~~
czr
Those are unfortunately not the same. The _second_ one (with an explicit radix
of 10) is correct; the first is not (or is at least a bit riskier). See
[https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Refe...](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/parseInt#Description) (ctrl+f
for "always").

~~~
acdha
But see also [https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Refe...](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/parseInt#Octal_interpretations_with_no_radix)
and especially [https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Refe...](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/parseInt#Browser_compatibility)
— that comment is increasingly stale unless you support browsers which are no
longer supported by their vendors like IE8.

~~~
czr
Good point. I've edited my comment to be more qualified.

~~~
acdha
The web is a fascinating study of advice that changed over time: there’s
something like a half-decade window where the two argument form became common,
with unsafe usage before and safe usage after.

------
JulianMorrison
So in summary:

JS map doesn't behave like anybody else's map.

JS argument passing prefers doing the wrong thing to interrupting the
programmer.

These two things conspire together and poor parseInt does its best with the
resulting garble.

~~~
horsawlarway
I don't really see any issue here.

JS map behaves sanely - It adds extra params, but I've had them be useful
every now and then, and I've _never_ had them cause a problem.

Arguments behave... Like arguments behave in JS. Arguments have ALWAYS been
variadic and accessible through the "arguments" variable within a function.
That's not the "wrong" thing, it's just a thing. If you want named arguments -
put them up top. Otherwise you'll get an array-like with everything passed.
This is entirely consistent with the language.

Finally - These two things conspire together to do _what_ exactly? This isn't
a subtle bug where it works correctly 99% of the time and blows in prod late
on a friday. This is an obvious error with even dead simple test cases that
clearly show the dev has messed up.

Unit test your shit, or hell, just run it once or twice before using it and
you're fine.

\---

Basically - you should know how your std library works. That includes JS. I
think this is pretty trivial. Worse, I actually find this behavior far more
intuitive than something like ConfigureAwait(false) in C#, for example.

------
maxxxxx
Stuff like this is what drives me crazy when I work with dynamic languages
like JS or PHP. I have seen a lot of code that looked perfectly fine but
suffered from unexpected casts or defaults.

I much prefer languages like C#, C++ or TypeScript where the compiler warns me
of such problems.

~~~
bastawhiz
This has nothing to do with casts or defaults. Map passes multiple parameters
and parseInt accepts multiple parameters. Being unaware of the functions
you're using is the problem, not some sort of weird gotchas of the language.
The code in the title makes multiple assumptions, and those assumptions all
proved wrong.

~~~
gameswithgo
A typed language would absolutely fail this at compile time.

~~~
zbentley
Only because map's third provided argument exists. If map only supplied
element and index to the callback, this would pass most type checkers I'm
familiar with, and would remain just as confusing.

~~~
happytoexplain
Precisely - this is an API design failure, pure and simple. Not a problem with
typing, optional arguments, etc.

~~~
wutbrodo
It's both. Silently accepting variadic arguments increases the odds of
something like this happening. This case wouldn't have happened without it.

Though as I said in another comment, best-effort parsing was a decision that
arose out of the Web ecosystem and its one of the few things about
Javascript's language design that can't br blamed on incompetence.

~~~
zbentley
What does this have to do with best-effort parsing?

"Variadic arguments" (really optional arguments with defaults/signature
overloading, which is only arguably the same thing) existed long before
JavaScript, and, indeed, the web. Optional arguments are also widely
considered to be good/useful.

------
martin-adams
>> "If the radix provided is falsy, then by default, radix is set to 10."

The official docs for parseInt says this:

>> An integer between 2 and 36 that represents the radix (the base in
mathematical numeral systems) of the string. Be careful — this does not
default to 10. [1]

I just found it confusing whether the author meant the default value is 10, or
if a falsy parameter (not undefined) turns out to be 10.

[1] [https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Refe...](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/parseInt)

~~~
kosievdmerwe
So looking at the rest of the documentation, the parameter not being set
causes the default radix to be 10 unless the number starts with 0 or 0x, in
which case they radix is 8 or 16 respectively. Though newer versions of JS no
longer support the octal syntax (it likely just caused bugs for people who had
initial zeros in their strings sometimes)

parseInt("0x10") == 16 parseInt("0x10", 10) == 0

------
jonfleck
Easy fix ['1', '7', '11'].map(Number)

~~~
nailer
Agreed. Number is not only more obvious than parseInt for other coders but
also defaults to base 10, whereas parseInt (at least historically) does not
(edit: ...if you've got leading zeros, see post below).

~~~
tomxor
> defaults to base 10, whereas parseInt (at least histortically) does not.

To clarify for others (because this probably sounds crazy): The default is
implicit like the rest of JavaScript, it will change base depending on
presence of the prefixes 0x (16) and 00 (8), it doesn't seem to have included
0b yet. The confusing bit was octal because as you can imagine some sources
might have base 10 padded with zeros, ES5 basically removes implicit octals in
parseInt to avoid this issue.

Arguably this is more of a problem with the ambiguous octal prefix than the
concept of using prefixes to determine base.

------
tntn
It is extremely awesome that JavaScript has conditioned a ton of the world's
programmers to think that needing to compose your function with the identity
function to achieve the desired result is not surprising or bad.

I will not be convinced that an implementation of map that gives different
results when there are identity functions in the middle is doing the right
thing.

------
ojosilva
This article made me curious to why a Base-1 or Unary Numeral base system has
not been implemented in parseInt() as noted by the OP, since the second
argument radix must be between 2 and 36:

    
    
        parseInt('1', 1)   // same error for '0' or '|'
        > NaN
    

The result is intriguing still as "NaN" seems to indicate a invalid input in
the first parameter instead of a invalid second parameter.

I've found that apparently there is no consensus [1] on Base-1 notation or
parsing, although my primary intuition is correct [2] in that a parser could
be written that would parse a "1" as a 1 base10, "11" as a 2 base10, "111" and
so on. The parser would probably look a lot like a simple length() function,
but that could vary with certain base-1 encodings like the ones used by the
Golomb Rice compression algorithms, which have each string end in "0" (unary
coding).

[1] [https://math.stackexchange.com/questions/371972/what-
would-b...](https://math.stackexchange.com/questions/371972/what-would-
base-1-be)

[2]
[https://en.wikipedia.org/wiki/Unary_numeral_system](https://en.wikipedia.org/wiki/Unary_numeral_system)

------
femto113
The extra args passed by map coupled with the optional extra args in standard
methods is the cause of a lot of confusion, but this feels like a missed
opportunity for demonstrating functional programming. In the end author
suggests

    
    
        ['1', '7', '11'].map(numStr => parseInt(numStr));
    

I think you'd learn something much more useful with

    
    
        function radixParser(radix) { return numStr => parseInt(numStr, radix); }
        ['1', '7', '11'].map(radixParser());
        > [ 1, 7, 11 ]
        ['1', '7', '11'].map(radixParser(8));
        > [ 1, 7, 9 ]
        ['1', '7', '11'].map(radixParser(2));
        > [ 1, NaN, 3 ]

~~~
fastball
If you're gonna use arrow functions, why not go all the way!

    
    
      const radixParser = (radix) => (numStr) => parseInt(numStr, radis);

~~~
femto113
because I still think

    
    
        function foo() { ... }
    

is a clearer way of indicating "I'm defining a function named foo". I tend to
use arrows only for anonymous methods.

------
kabwj
[https://imgur.com/EAaJho6.jpg](https://imgur.com/EAaJho6.jpg)

Thanks Medium! Great readability.

------
mavci
It’s reminded me to this funny talk
[https://www.destroyallsoftware.com/talks/wat](https://www.destroyallsoftware.com/talks/wat)

------
pkaye
I guess JavaScript like to use the "principle of most surprise"?

------
tonymet
let's work to bring back brevity the answer is that map is passing extra args
just use an arrow func to pass args to parseInt explicitly there saved you a
minute scrolling to the bottom

------
evolutionxbox
Because parseInt isn't a unary function?

------
Skywing
This isn't a quirk of JS at all. It's somebody purposefully calling `parseInt`
incorrectly.

------
datpuz
tl;dr: parseInt takes an optional second argument, the radix. It defaults to
10 (so the number is parsed in base 10). Since map() passes in each item in
the array as the first argument, and the index as the second, you're parsing
each string with the radix of whatever the index is.

Not too weird IMO.

------
jfengel
To quote the tl;dr: ['1', '7', '11'].map(parseInt) doesn’t work as intended
because map passes three arguments into parseInt() on each iteration. The
second argument index is passed into parseInt as a radix parameter. So, each
string in the array is parsed using a different radix.

That's hilarious. Everybody loves the syntactic sugar that makes things easy,
until the unexpected point where it makes things very hard.

~~~
tntn
What is the syntactic sugar here? I mostly see a map that doesn't behave how
anyone would expect.

~~~
JoeNr76
The syntactic sugar here is that map and parseInt can be called without
specifying all parameters. (But, since JS has no function overloading, it's
the same function you're calling.)

~~~
zbentley
Optional/defaulted function arguments are an extremely useful feature of many
programming languages; are you arguing that they are always harmful?

~~~
JoeNr76
Point me in my remark where I said that they are harmful. I was answering the
question what the syntactic sugar was.

------
seszett
TL;DR, parseInt() takes the number base as a second argument, and map() passes
three arguments (value, index, whole array).

Using directly like this a function with map() is just incorrect, the correct
way to do it is:

    
    
        ['1', '7', '11'].map(x => parseInt(x))
    

_edit_ I'm getting downvoted, it doesn't matter much but I don't understand it
when the most upvoted comment seems to say more or less the same?

------
IloveHN84
Because It's JavaScript.. stop using unsafe languages, really

~~~
jmull
A bit of a BS comment since there are no languages that will prevent you from
misunderstanding the values passed into or returned from whatever
methods/procedures/jump destinations you're working with.

More generally, general purpose languages don't provide safety guarantees in
the problem domain. They can provide certain guarantees in the solution
domain, but it's left to the programmer to compose the elements of the
language into a correct solution.

(In my experience, by far the biggest barrier to a correcty solution in the
problem domain is that no one actually knows what that is, much less has
expressed it. Instead, people express certain specific behaviors they think
the system should have, from which the programmer needs to extrapolate the
actual requirements -- not straight-forward since to a greater or lessor
degree the expressions will be vague, self-contradictory, self-defeating,
and/or incoherent. Then they need to compose those requirements in the
solution space. BTW, the language is just a part of the solution space and
"safe" languages are usually only referring to static checks, which the
solution space often consists of distributed components which aren't strictly
controlled in lock-step by the same static sources, meaning the static
guarantees are useful, but in a limited way.)

------
cellis
Recently I learned that in Javascript,

    
    
      [5 * 5] * 2 // 50
      [5 + 5] * 2 // NaN
      1 + [5 * 5] * 3 // 76
      1 + [5 * 5] - 1 // 124
    

Interestingly, even Typescript will not ( by default ) catch this class of
bugs.

~~~
megous
[5+5]*2 is not NaN

~~~
cellis
True, I was playing around to get examples in runJS. I think I mis pasted that
one. Oh well.

------
alexk7
This is _not_ one of the weird things about Javascript. When you use a
function (like map), always check the documentation to know its behaviour and
don't assume it's similar to some similarly named function from another
language. End of story.

~~~
cbm-vic-20
> don't assume it's similar to the same named function from every other
> language

~~~
lucideer
Not every language has such a function.

Even languages that do have different versions of it on different objects
(e.g. Scala's zipWithIndex)

------
nullwasamistake
Use Typescript. Regular JS just has too many gotchas to be usable in large
applications.

You can crank up the settings in TsLint and never worry about things like this
again

~~~
acdha
Did you try testing that? The TypeScript compiler does't say a thing about
that code:

[https://www.typescriptlang.org/play/index.html#src=alert(%5B...](https://www.typescriptlang.org/play/index.html#src=alert\(%5B'1'%2C%20'7'%2C%20'11'%5D.map\(parseInt\)\)%3B)

TSLint similarly reports nothing even with the tslint:all ruleset:

[https://palantir.github.io/tslint-
playground/?saved=N4Igxg9g...](https://palantir.github.io/tslint-
playground/?saved=N4Igxg9gJgpiBcIDaByAjCgNAAhQdi1zQwF0A6AWwEMAHAChqoCcBnGASQDsAXASgG4AOpxCZwETgDMAlgHMEIAPSLsATQgBXbGCqdsFaNMkBPbNwAWMbRJmyNTKt2kTslplYDu0i9mOam2BAenGTCytgAslQA1lYs9lYWjr7+2ABWLC7SLNgAblQANtJQOLpQ2EYpWkwaetI8EGEqEBYwAdnxMCw43EymYJZg0fWyZpbWnJkFVpIQAW1McyyhnMLAwtib2IIgMAAe3DCcUCw78NhIO9wsRTzwhQU7JJgbWzs106cI2MAAvtjhKhQcpAqDeZycQrYD5dbBlMYwaQBSBSOT2RwQ4S-YQgX5AA)
[https://palantir.github.io/tslint-
playground/](https://palantir.github.io/tslint-playground/)

~~~
nullwasamistake
Doesn't seem right, are all the strict compiler options enabled?

~~~
acdha
Yes, can you post the configuration you used for your original claim?

