DEV Community

Cover image for Stop abusing .map()!
Pan Seba
Pan Seba

Posted on

Stop abusing .map()!

Every once in a while when I do code review or visit StackOverflow I stumble upon code snippets that look like this:

const fruitIds = ['apple', 'oragne', 'banana'];
fruitIds.map((id) => {
   document.getElementById(`fruit-${id}`).classList.add('active');
});
Enter fullscreen mode Exit fullscreen mode

So as you can see it's just a simple iteration where for every element in the fruitIds array we add active class to a certain HTML element in a DOM.

Many programmers (especially new) wouldn't notice anything wrong with the code above. However, there is a one major issue here - the usage of .map(). Let me explain.

What is wrong with .map()?

Well, there is completely nothing wrong with this particular array method. In fact, I think it is very handy and beautifully wraps one of the iteration patterns - mapping.

In simple words, mapping is an operation which applies a function to every element of a collection and returns a new collection with elements changed by the mentioned function. For example, if we have an array of numbers const nums = [1, 2, 3, 4]; and would like to receive a new array of doubled numbers, we could map the original array to a new one like this (in JavaScript):

const biggerNums = nums.map((n) => n * 2);
// >> [2, 4, 6, 8];
Enter fullscreen mode Exit fullscreen mode

The biggerNums array would consist of numbers from the original nums array multiplied by 2.

Notice how .map() is used - we assigned the result of this method to a new variable called biggerNums. I have also mentioned earlier that mapping is an operation that returns a new collection of elements. And this is the very reason the code snippet showed at the beginning of this article is wrong. The .map() returns a new array - always - and if we don't need that array, we shouldn't use .map() in the first place. In this particular case (simple iteration) a different array method should be used - .forEach() - which is specifically designed for such cases. It doesn't return a new collection, it simply walks through an array and invokes a callback function for every element allowing you to do something for each of them.

So the correct version of the mentioned snippet should look like this:

// good way
const fruitIds = ['apple', 'oragne', 'banana'];
fruitIds.forEach((id) => {
   document.getElementById(`fruit-${id}`).classList.add('active');
});
Enter fullscreen mode Exit fullscreen mode

We don't need a new array so we simply iterate over the fruitIds array and add the active class to an HTML element for each of the array items.

Okay, but why should I care? .map() is shorter and easier to write than .forEach(). What could possibly go wrong?

Consequences of abusing .map()

One of the worst consequences of abusing .map() is the fact that it returns a new redundant array. To be more specific - it returns a new array of the same size as the one this method was called on. It means that if we have an array of 1000 elements, .map() will return a new array of 1000 elements - every time.

In JavaScript, all functions return a value. Even if we don't use the return keyword, the function will return undefined implicitly. That's how the language has been designed. This rule also applies to callbacks - they are functions too.

Having said that, let's get back to the original example:

// wrong way
const fruitIds = ['apple', 'oragne', 'banana'];
fruitIds.map((id) => {
   document.getElementById(`fruit-${id}`).classList.add('active');
});
Enter fullscreen mode Exit fullscreen mode

What happens here? An array of fruit IDs is created and then it's mapped to another array of the same size. Even though the array returned by .map() is not used, it does take place in memory. This new (unused) array looks like this:

[undefined, undefined, undefined]
Enter fullscreen mode Exit fullscreen mode

It's because the callback passed to the .map() method does not have the return keyword and as we know, if there is no return, undefined is returned implicitly.

How bad is it? Very bad. In this particular example it won't bring any serious consequences - there are only three items in the array so creating another three-element array won't cause any problems. However, the problem arises when we deal with big arrays of complex data. If we want to iterate over an array of five thousand objects and we abuse .map(), we create another array of five thousand elements - undefineds. So we end up storing 10 000 elements in memory from which a whole half is redundant. It is a very non-optimal practice and in some scenarios it may even lead to the application overload. This is why we should pick right methods to the right tasks.

Summary

There are many practices that are essentialy bad, but the negative consequences will start to be visible only when dealing with bigger datasets. One of such practices is abuse of .map(). When operating on small arrays, it won't cause any hurt. But when we make this mistake with a bigger array it will start overloading our application and it may be quite hard to debug.

This is why we should never let it pass by and whenever we see this abuse, we should take care of it. I hope now you understand why.

Latest comments (47)

Collapse
 
vinayaksinghcs profile image
VinayakSinghCS

Nice article will make sure to use forEach instead of map wherever necessary

Collapse
 
mikaleb_77 profile image
Mikaleb

Yeah I know, but Object.keys, Object.whatever with different results with unknown objects (if no ts)

Collapse
 
antonioaltamura profile image
antonioaltamura • Edited

the article would have been complete with a bechmark.
Here we are jsbench.me/ujkub8rcv2/1
forEach seems to be the fastest in that snipped. map seems to be around 20% slower and for..of 15% slower.

Collapse
 
maixuanhan profile image
Han Mai

Don't worry, it will make your code smelly only. Memory will not be an issue.

Collapse
 
mikaleb profile image
Mikaleb

The day we have map() working for objects it'll be everywhere :')

Collapse
 
brianmcbride profile image
Brian McBride

I guess incorrect use of Map is really only an issue if it is harming the app performance. Someone dealing with huge datasets will see that. Otherwise the returned array is going to be garbage collected after the containing function exits.

Still, better to use the right tools. And you know, there is a lot of docs for that!
developer.mozilla.org/en-US/docs/W...

It is good to know many of these. I mean, who uses copyWithin()? I mean, handy, but I haven't needed that edge case myself. includes() and some() are ones I don't see used often, but are handy (some is sort of like findIndex but returns boolean).

Collapse
 
davidmaxwaterman profile image
Max Waterman

I guess incorrect use of Map is really only an issue if it is harming the app performance.

I think it's more of an issue on developer performance - ie the word 'map' has a meaning that the developer understands, and if they read 'map', the notice that the results of the map are thrown away, it leads to a brain fart (aka 'wtf?'). forEach has no such smell.

Collapse
 
mfbx9da4 profile image
David Alberto Adler

This kind of micro-optimization is the devil's dust. Premature optimizations like this often lead to ugly code. Tools not rules!

Collapse
 
darrylhodgins profile image
Darryl Hodgins

map with side effects… yes, that's bad practice. But one could use map here. It might not be necessary in the trivial case, but if we want to throw a filter in there as a sanity check, this makes for readable code:

const fruitIds = ['apple', 'orange', 'banana', 'carrot'];  // carrot is not a fruit

fruitIds.map(
  (id) => document.getElementById(`fruit-${id}`)
).filter(
  (element) => !!element
).forEach(
  (element) => element.classList.add('active')
);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
davidmaxwaterman profile image
Max Waterman

Why not just the following?

const fruitIds = ['apple', 'orange', 'banana', 'tomato'];  // tomato is a fruit

fruitIds.forEach((id) => {
  const element = document.getElementById(`fruit-${id}`);
  element?.classList.add('active');
});
Enter fullscreen mode Exit fullscreen mode

Is that not equivalent, clear, and only iterates the list once instead of ~3 times?
What are the pros and cons of chaining array methods like the above? Perhaps easier to add another method, eg sort(), or some other filter()...but in this instance, not so much, imo.

Collapse
 
fawazsullia profile image
fawazsullia

I'm guilty of doing this. Makes total sense, since the arrays I'm iterating are mostly objects and are huge. Thanks for the article.

Collapse
 
pranaythesingh profile image
Pranay Singh

Nice Post

Collapse
 
youdev profile image
Younes

There is also a performance cost to consider when looping:

leanylabs.com/blog/js-forEach-map-...

Collapse
 
tanth1993 profile image
tanth1993

I totally agree. when I need return something I will use map(), otherwise I use forEach()

Collapse
 
ugizashinje profile image
Никола Шијакињић

Dude, it is not redundant map, it is immutable one. There are reasons why people are using it.

Collapse
 
hyungjunk profile image
hyungjunk • Edited

I totally agree to the point of the post. One thing I wonder is if map is used just like the bad practice example, when does garbage collector collects that isolated, unused array? will it exists for long?

Collapse
 
ccampanale profile image
Christopher Campanale

Stop telling me what to do! I do what I want!

😘

Some comments may only be visible to logged-in visitors. Sign in to view all comments.