loading...

I Use For Loops Almost Always In Javascript

beggars profile image Dwayne Charrington ・2 min read

Recently, Kent C. Dodds posted about an instance where using a for loop dramatically resulted in better performance.

Alt Text

When you delve into how filter, map and reduce methods work (based on callbacks), really it is of no surprise to anyone who sees the results for themselves that these methods are a lot slower.

Replying to Kent's Tweet, I mentioned how I always use for loops and how their performance is unrivalled:

Alt Text

Really, it did not take long for the defensive Tweets from other devs on Twitter.

Alt Text

For loops are not just about optimisation

Yes, for loops are faster and yes, if you're not careful they can have unintended side effects as a result of not using better-suited methods like filter or reduce, but I don't use for loops with the sole purpose of optimising my code.

In most cases, you won't notice any difference in performance for small data sets. When you're dealing with tens of thousands of items like Kent was, then you definitely will.

If you are working with promises and want to use async/await then you need to use a for loop to do that as those beloved one-liner methods do not support them very well.

If you want to be able to control the flow of the loop (break out of it), then you will want to use a for loop.

While for loops will add to the size of your code and not be as "clean", we need to stop focusing on clean code and focusing on clear code. To me, nothing is more immediately clear than a for loop.

I am also not advocating against using those specific methods. I still use filter, map and reduce where applicable. In most instances, I find for loop is easier to write, even if it usually results in more code.

As front-end and Javascript developers, we need to stop pretending there is one right and true way of doing something in Javascript. Because there is not. Everyone has opinions and preferences, we need to accept that we are all different.

Posted on by:

beggars profile

Dwayne Charrington

@beggars

Lead front-end developer @ ia // Aurelia.io core team, 11 years experience, amateur professional developer.

Discussion

markdown guide
 

I usually opt for a loop due to simplicity and readability, not necessarily performance. I found the premature optimization comment that you received pretty shallow because of that. With something like .reduce(...), there is mental overhead to parse it and figure out what is going on. If it isn't calculating some type of sum or total, then it is being used only for the loop aspect. In that case, just use a loop!

It seems like many JavaScript devs look at a collection that they want to loop over and say "hmmm how can I use a SICK reduce on this". It is clever and shorter, but is it more readable? Most of the time, I would disagree.

JavaScript is a language that has some functional features incorporated into it, which is great. But I think taking that to the extreme and trying to make everything functional is not always the best approach. I'm not sure why the functional stance has become so prevalent in the JavaScript community.

 

You hit the nail on the head here, Ryan. I have no idea where the functional mindset originated from. I know quite a few JS "thought leaders" are strong advocates for the functional approach and I honestly think React shares a lot of the blame for this in recent years.

Because of design choices and mistakes in React itself lending itself to things like hooks being created, their anti-classes/oop approach has resulted in an ecosystem of developers who don't question the approach and blindly assume that Facebook is the be-all and end-all of JS and follows them into the mouth of the volcano.

Just based on my experiences (and sounds like yours as well) I have seen what happens when "clever coders" think they are writing "clever code" and it always almost results in the code being rewritten eventually because it's trying to be too smart and nobody can work out what it is doing.

I have seen a lot of people advocating for using map as a means of looping over items in arrays where a for loop would be more appropriate. And in the case of reduce especially, I have seen some pretty crazy stuff.

And truth to be told, I have fallen into some traps with reduce before forgetting the first argument in the initial value or the previous value, because I am used to how methods like forEach return the value in the iteration first.

 

My ex colleague used to use fancyreduce all the time which made it so much harder for me as a Junior to understand what was going on. Fancy one-liners are cool on codewars or similar sites but not so much in actual code that everybody in the team needs to understand.

 
 

"I'm not sure why the functional stance has become so prevalent in the JavaScript community." Generally, it's simply because this paradigm was the way it was learned from the beginning. Paradigms, new or otherwise, by definition are difficult to incorporate. You make a very good point, Ryan.

 

So, I've noticed this pattern in my code a lot:

const ary = await Promise.all(...);
for (let elem of ary) {
    ...
}

Knowing that for ... of loops are not as fast, I started using the following construct:

const ary = await Promise.all(...);
while (ary.length) {
    let elem = ary.shift();
    ...
}

This post got me wondering how my loop compares, so I tested my way and other loop constructs here. It's interesting how much slower these other loop constructs are. Needless to say, I'm going back to the traditional for loop now!

Here are my simple test results:

Testing vanilla:      4.1ms
Testing lodash:      25.4ms -- 518% slower
Testing es6-for-of:   7.3ms --  78% slower
Testing forEach:      6.1ms --  48% slower
Testing map:          9.2ms -- 125% slower
Testing reverse:      3.4ms --  17% faster
Testing shift:       20.9ms -- 408% slower
 

I ran your tests on a longer array, and found increasing the array length magnifies the performance advantage of the vanilla for-loop quite a bit:

Testing vanilla:     120.9ms
Testing lodash:      2561.8ms -- 2019% slower
Testing es6-for-of:  347.6ms -- 188% slower
Testing forEach:     1179.2ms -- 875% slower
Testing map:         1517.9ms -- 1156% slower
Testing reverse:     166.4ms --  38% slower
Testing shift:       7316.3ms -- 5952% slower

I used an array of length 1000, and since I'm impatient I cut down LOOPS to 1e5.

Also to be noted is that the 'shift' loop is not equivalent to the others, because it destroys the input array in the process. I dealt with that (and with creating a long array) by setting up

const someNumbers = Array.from({ length: 1000 }, (v, i) => i);

on the top level, and copying it into testArrayShift() on each iteration of the outer loop.

It runs a bit against my intuition that the reverse loop becomes slower on longer arrays -- I expected it to have a (small) performance advantage.

Actually I'm used to writing the reverse loop as

for (let i = someNumbers.length; i--; ) {
    tmp += someNumbers[i];
}

(and that's the version I actually tested)
but again a surprise: your version is actually a bit faster.

Altogether, I guess I'll just stick to vanilla for-loops from now on.

 

I added .reduce() to the test, and on a long array it's only about twice as slow as the vanilla for-loop.

 

For loops can be clean. In ES6 we got for..of loops, they are declarative and work with more data types than just arrays.

for (let variable of iterable) {
  // statement
}

My only problem with for loops is that it takes discipline to extend their behavior in a "clean way." It is just so easy to go and add little if statement here and there (specially if you are under pressure) and before you know it you have block of code that is dificult to deal with.

 

I love for..of loops, they are the only kind of for loop I really write these days. Especially being able to do something like for (const value of object.entries()) which makes working with different types of data (not just arrays) so much nicer.

There are definitely some traps you can fall into (side effects from modifying the reference if you forget to create a new array/collection) but if you're aware of them, it's not much of a problem.

 

Thank you for this article. I've used a lot of for loop, and recently i feel like using for loop makes me something like an "outdated" js coder. But yeah thanks, now when I use for loop I have the mindset that it gives better performance, and the future of js frameworks is all about performance.

 

Definitely nothing wrong with using for loops. I do recommend knowing and learning the functional methods filter, map and reduce but you don't have to use them in most cases.

It's sad that JS has got to a point where a staple of every language (a for loop) has become something many in the community argue against and seemingly for no good reason.

 

well around 2-3 months ago i always use for loop almost all the time, but then since I started using reactjs, I use now filter and map only if don't need a break statement

 

At one point I was somewhat well known in the Haskell community, so functional programming feels very natural to me. And I only use for loops in JavaScript, since I have found the error messages and debugging support for map/filter/fold to be too unpleasant to use.

 

Really good stuff. This is similar to what I heard Kyle Simpson say he is so intentional about what variable declaration he uses and where. It was cool he said that when he is using a variable that will be scoped and used though out a function he would use var to declare the variable but when creating a for loop and declaring a variable inside of it or to declare his iterator he would use let. He explained the more in the code he sticks to this thought process hopefully the easier it is to read. It really inspired me to not just ever say one thing fits all like always use === or filter or reduce they all have their place let's just be intentional.

 

I rarely use reduce because I never remember how to use it lol. The reason why I would use filter or map over a normal loop is for readability. If I see a filter I can already guess what I was trying to do in that part of the code rather than seeing a for loop and guessing. That being said I'm not working on large data so performance is never really a big concern.

 

That last paragraph really hits the nail on the head. Javascript was designed so that there are many ways to write things, so obviously the best way to write something is how you prefer it.

These people who care more about everyone doing what they are doing than actually writing good code are toxic.

 

I'd definitely agree with you for Reduce, I find the way that works and is structured to just be inherently confusing.

Map and Filter though I will usually default to, rather than a for loop. For me it's all about reducing intellectual overhead and ensuring my hands can keep up with my brain so that I don't break my flow.

 

This topic has been worked so many times. I don't know what Kent did. Maybe he itereated over the array first to filter and then did iteration for reduce and in terms of for loo only one iteration was done. Don't know. But the truth is that in case of loop you should almost always use filter/map/reduce. First of all it's always better to have a declarative code. It produces less confusion, which as Kyle Simpson says - is a reason of most bugs. I'm not talking about one-liners vs couple line of code. Experienced programmer should know that it's not the amount of code that counts. First of all obviously the name of this array methods makes you closer to quick understand what youre about to see. You look at the code and it's a matter of second when you notice: OK, so we have some mapped data, filtered one, reduced to one value etc, whereas with for loops you know absolutely nothing about those iterations until you work them through line by line. You can't really know the outcome, purpose, anything. Secondly it requires some of the good practises of programming when using map/filter/reduce. It's rare to see some bloated code within this methods. If one sees that during code review than it's clear that someone made it wrong. In for loops there are so many place where you can make sth wrong, where somebody may overlook what you wanted to do. There is no really required structure, it probably won't be pure. When it comes to breaking the loops, you can also do that without for. Just simply use Array.prototype.every. It will do pretty much same thing.

 

Nothing wrong with loops indeed. However I find filter, reduce and map useful as they are immutable and can be chained. How do you deal with these use cases?