DEV Community

loading...

A Trick With Map

jckuhl profile image Jonathan Kuhl ・3 min read

The Problem

Consider the following problem I came across on r/learnjavascript: Why does [1,3,11].map(parseInt) result in [1, NaN, 3]?

The Answer

It is common for new developers to be unaware of how map works and how it feeds parameters to the function it is provided. We're all aware that the first parameter it gives is the element in the array we're currently iterating over.

For example: [1,3,11].map(element => console.log(element)) gives us an output that lists 1, 3, 11.

But the fact is, map actually feeds three parameters to the given function. Map provides the current element being iterated over, the current index we're at, and the array being iterated over.

[1,3,11].map((element, index, array) => //... )

We can see this by changing our console.log by referring to it directly rather than wrapping it in an anonymous function:

[1,3,11].map(console.log)
1 0 [ 1, 3, 11 ]
3 1 [ 1, 3, 11 ]
11 2 [ 1, 3, 11 ]

This is pasted directly from the node REPL. You can see here, we get on each line, the element, the index, and the array.

So back to the original problem, why do we get [1, NaN, 3]?

Because these three arguments are being passed into parseInt. I believe that many new developers forget that parseInt actually takes two arguments, more than they would forget that map provides three arguments. parseInt takes a number and the radix. The radix tells parseInt which number system we're using. 0 is decimal, 2 is binary, 3 is trinary, 8 is octal, 16 is hex, and so on and so forth.

parseInt(11, 3) outputs 2 because that's it's trinary equivalent.

So on each pass in map, parseInt looks like this:

parseInt(1, 0);
parseInt(3, 1);
parseInt(11, 2);

The array argument is ignored because parseInt only takes two arguments. The number provided becomes the element from the array and the radix comes from the index.

On the first pass, we're converting 1 to decimal, which is of course, 1. A radix of 0 and 10 are decimal, so nothing changes with decimal numbers. On the second pass, we get NaN because the value 1 is invalid for a radix. And on the third pass, we're converting 11 to 2. 11 in binary is of course, 3. We have a 1 in the 2^1 position and a 1 in the 2^0 position, 2^1 + 2^0 = 2 + 1 = 3.

In short, the reason we get unexpected values, is because map provides more arguments to the parseInt function than we desired.

How do we fix this?

The solution is very simple, we don't directly pass a reference to a function like parseInt to map. Instead, it's best to wrap it in an anonymous function first:

[1, 3, 11].map((element, index, array) => parseInt(element) );

Of course you can omit the index or array parameters if you aren't using them, I left them in for illustration

Then you can pass your arguments to the function as needed.

Now this is only necessary for functions that take, or potentially take, multiple arguments. If you want to map with a function that only takes one argument, feel free to pass it the reference directly.

const square = x => x * x;
[1,2,3].map(square);
//  [1, 4, 9]

Just be careful to be sure whatever you pass into map, and this goes for foreach and many of the other array methods as well, only takes one argument.

You can read more about how parseInt works on the Mozilla Developer Network here.

Happy coding.

Discussion

pic
Editor guide