DEV Community

Cover image for Stop using for loops. Here's why.
Jack Shen
Jack Shen

Posted on

Stop using for loops. Here's why.

It is current year new Date().getFullYear(), and still I still see many JavaScript devs using and even recommending the use of for loops. You yourself may even be using for loops on a regularly basis. Here's why you shouldn't and what you should use instead.

Why shouldn't you use for loops?

for loops and their usage are largely a holdover from imperative languages, in particular C-likes and their derivatives. While they are quite versatile, they suffer many flaws.

Let's start with clarity. Let's face it, for loops just don't make the prettiest blocks of code, and only increase in ugliness the larger they get. In complex algorithms, they can quickly become enigmatic, nigh unreadable save for commenting. And while comments are an expected courtesy, there's a reason why bad documentation is such a frequent joke amongst devs.

Secondly, for loops increase the chance of unwanted side effects. for loops--and while loops-- are what's known as control statements, meaning they must be placed inside of a function and can not be used as standalones. This inherently increases the chance you'll end up manipulating variables outside of the loop's scope. Unfortunately, this clashes with more modern programming paradigms in functional programming, specifically precluding anything involving for loops from being a pure function.

Thirdly, for the exact same reason as above, for loops are prone to requiring mutation in some form. A lot of the time, this is also unavoidable because all for loops can really do is facilitate iteration. By themselves, for loops are kinda useless, which means you have to declare a variable to mutate, or mutate the original array. Again, this is at odds with functional programming principles.

What should you use instead?

As of ES6, Array.prototype methods have been introduced that make for loops obsolete in most ways.

Let's recap the reasons against for loops and how these methods solve them.

1. Lack of clarity.

In ideal circumstances, good code should be self-evident and self-explanatory. Array methods bring us closer to that ideal, they are almost descriptive enough to read as some sort of "shorthand natural language".

myArray.every(value => value > 40);
// Check every element of myArray is greater than 40.

myArray.filter(word => word.length > 6);
// Filter out every element in myArray longer than 6 characters.

myArray.sort();
// Sort myArray (lol)

Contrast these with their for loop equivalents:

const checkEveryValueGreaterThanForty = myArray => {
  for (let i = 0; i < myArray.length; i++) {
    if (myArray[i] <= 40) return false;
  }
  return true;
};

const checkEveryWordLongerThanSix = myArray => {
  for (let i = 0; i < myArray.length; i++) {
    if (myArray[i].length <= 6) return false;
  }
  return true;
};

// Most devs already know to use Array.prototype.sort(), consider this
// a more extreme example of what enigmatic for loop code can look like
function bubbleSort(myArray) {
  let isSorted = false;
  while (!isSorted) {
    isSorted = true;
    for (let i = 1; i < myArray.length; i++) {
      if (myArray[i - 1] > myArray[i]) {
        isSorted = false;
        [myArray[i - 1], myArray[i]] = [myArray[i], myArray[i - 1]];
      }
    }
  }
  return myArray;
}

Yikes. As you can see, these are ugly, long, and you actually have to spend time reading through the entire thing to figure out what's going on. Using array methods cut them down into a single beautiful line with an immediately recognisable purpose.

2. Side effects.

Many array methods are higher-order-functions -- they take in a callback function as one of their parameters. By explicitly calling array instance methods, iteration can be performed in something like a self-enclosed scope. You may have already noticed this in the previous examples, where loops inside functions were replaced by one-line methods.

3. Possible mutation.

Accessor methods are handy for modifying an array, and iteration methods replace most if not all functionality provided by for loops without touching the original array.

const doubleArrayValues = myArray => {
  const newArray = [];
  for (let i = 0; i < myArray.length; i++) {
    newArray[i] = myArray[i] * 2;
  }
  return newArray;
};

Notice in this example we've had to create a new variable newArray. This is necessary if we do not wish to change myArray, but even still, we must mutate newArray in order for the algorithm to work. What if we tried an array method?

myArray.map(value => value * 2);

We have eliminated the need for a new variable without compromising our desire to keep the original array unchanged.

In addition, there are mutator methods such as Array.prototype.pop(), Array.prototype.sort(), and Array.prototype.splice(). What this provides, especially when chaining methods, is greater flexibility in how you approach an algorithm.

That's not to say you can't mutate using non-mutator methods. However, you'd have to go out of your way to mutate the original array:

myArray.forEach((element, index) => {
  myArray[index] = element * 2;
});

Kinda defeats the purpose, but once again, you have the flexibility to do so if you really want to for some reason (you shouldn't).

The bottom line.

Array methods, when applied properly, are incredibly powerful and render for loops all but obsolete. It is possible to write very pure functions, without mutating any arrays or objects and without creating any variables or constants. For example, the popular Combination Sum interview question can be purely solved using only recursion and array methods.

// Array.prototype.reduce() is incredibly powerful when applied correctly

const combinationSum = (sum, numbers) =>
  numbers
    .filter(filterNumber => filterNumber <= sum)
    .reduce((path, currNumber) => {
      if (currNumber === sum) return [...path, [currNumber]];
      else
        return combinationSum(sum - currNumber, numbers).reduce(
          (allCombos, currCombo) => [...allCombos, [currNumber, ...currCombo]],
          path
        );
    }, []);

Of course, for loops are still around for a reason. Perhaps you have a specific application where it's not appropriate to, or just can't use array methods. In those cases, take care to give your counter a proper name if you intend to use it -- something like let index = 0 instead of just let i = 0. In any other case, try your hand with array methods.

Top comments (37)

Collapse
 
jkhaui profile image
Jordy Lee • Edited

... And it's articles like this that make me think we were better off before anyone could publish anything on the internet.

The main problem I have is your piece is actually well written, thus it is probably going to be taken as gospel by newbie devs.
I enjoy the ease of writing callback functions instead of for loops as much as anyone, but it is such a biased outlook to claim that this is always the right solution.

Why don't you mention the areas where imperative loops outshine higher order functions? E.g. the fact that a for loop is ~10x faster than a forEach callback

Collapse
 
jackshen profile image
Jack Shen

E.g. the fact that a for loop is ~10x faster than a forEach callback

I believe this is untrue. Can you substantiate this claim?

Collapse
 
jkhaui profile image
Jordy Lee • Edited

It's widely known that loops based on callback functions generally perform much worse than imperative loops. Here's an example, where forEach is 96% slower than a vanilla for loop: stackoverflow.com/questions/438217...

C'mon man, I appreciate that you want to share your knowledge, but if you are going to write posts with such provocative titles then you need to know this stuff. I am a big fan of functional programming and much prefer it over OOP, but the aspect I dislike most about FP is the performance hit from making everything immutable

Collapse
 
matgott profile image
matgott

Try yourself.

let arr = [];
for(x = 0; x < 100000; x++) {arr.push(Math.random());}

function oldFor(d) {
aux = [];
const t0 = performance.now();
for(x=0; x < d.length; x++) {
aux.push(d);
}
return (performance.now() - t0);
}

function eachFor(d) {
let aux = [];
const t0 = performance.now();
d.forEach(v => {
aux.push(v);
});
return (performance.now() - t0);
}

oldFor(arr);
eachFor(arr);

Thread Thread
 
joey542 profile image
joey542

Okay. This is your code in typescript with a 10M length array. The forEach time is double.
Image description

I made another for just testing the other loops with a 10M length array.
Image description

So... the conclusion is... If you want to loop through big arrays just use the old for loop. And I made an account just for to send this comment.

Collapse
 
davidjames profile image
DavidJames • Edited

wow, this is terrible. I'd suggest "deleting this post" if you were to ask.

Collapse
 
chechenev profile image
Maxim Chechenev • Edited

The title is so clickbait, it can confuse people a lot.

Your examples are not about "stop using" but about situations how/when to use filter, reduce, etc.

I think you should provide more specific examples and real life use cases to explain your opinion, otherwise it's a really bad advice.

Collapse
 
macsikora profile image
Pragmatic Maciej • Edited

I would like to filter all those kind of articles.

To be clear I appreciate your effort, but radical attitudes saying that you should not use x or y is harmful. Lately there was such attitude for not using reduce, and I don't agree with that in the same way I don't agree with your article.

Also I think you are by intention making such clickbait title.

Collapse
 
jkhaui profile image
Jordy Lee
const filterClickbaitArticles = () => fetch('https://dev.to/api/articles')
  .then((response) => {
    return response.json();
  })
  .then((articles) => {
    const BAD_ARTICLE_REGEX = /(why)(\b\s.*\s\b)(is bad)/gi;

    return articles.filter(title => !BAD_ARTICLE_REGEX.test(title));
  });

Collapse
 
macsikora profile image
Pragmatic Maciej

Good one 😁

Collapse
 
pavermakov profile image
Pavel Ermakov

If you are working with very large lists of data, the for loop shines like a diamond

Collapse
 
lluismf profile image
Lluís Josep Martínez

Or if you read a very big file, or a result set of unknown size.

 
idanarye profile image
Idan Arye

+1 about reduce. Consider #3 in dev.to/thejohnstew/5-programming-p...

const exampleValues = [2, 15, 8, 23, 1, 32];
const [truthyValues, falseyValues] = exampleValues.reduce((arrays, exampleValue) => {
  if (exampleValue > 10) {
    arrays[0].push(exampleValue);
    return arrays;
  }

  arrays[1].push(exampleValue);
  return arrays;
}, [[], []]);

It uses reduce to feel functional and hip, but in practice it just mutates the arrays and passing them as is (they are mutated, but it's the same reference) to the next iteration.

But even proper uses of fold/reduce are usually clearer and simpler when done with loops, because the whole point of reduce was to emulate foreach in purely functional languages.

Collapse
 
timhlm profile image
moth • Edited

All of these are tools that serve different purposes. A few points I'd like to make:

1.) Composition

The prototype methods map, filter, reduce are useful because they avoid side effects and mutation by returning a new instance of the array, but they're also composable (or chainable in this chase), arguably their biggest benefit.

The other methods, sort, every, and forEach are either not composable because they don't turn arrays, or they actually does mutate in place (which you do touch on in the article with forEach, much appreciated, but sort is the bigger offender).

2.) OG for loops are generally faster

hackernoon.com/javascript-performa...

Not only that, but they can take exponentially less resources. Imagine you have a data set of N objects, that all get mapped, filtered, and reduced via prototype methods. That's three new versions of your data of N length that have to be in memory now, and you've gone through the list 3 times. There're real performance implications to this. If you can do it with a for loop in one pass, that may be a better option in context.

3.) For...of

The new for...of operator is outstanding, and plays better with async/await than the array prototype methods. Take a composition function for example:

function compose(fxList =[]) {
  return async function (initialInput) {
    let aggregatedValue = initialInput
    for (const fx of fxList) {
      aggregatedValue = await fx(aggregatedValue)
    }
    return aggregatedValue
  }
}
Enter fullscreen mode Exit fullscreen mode

This is basically a reduce, but it is easier to understand and write with a for...of loop. Try an implement this with the reduce prototype method, and I think you will see that it's not as straightforward.

Not only that, but For...of's support iterables, one of JavaScript's most powerful features. To use Array prototype methods, you have to spread them across a new array and you lose the context about what the data structure was initially. You then lose all the benefits of them- whether it's a Set or Map or some custom iterable. To me, that's pigeonholing yourself to array features and JS will evolve past that.

Always appreciate a different opinion though. Thanks for writing this and starting the conversation!

Collapse
 
jserrao profile image
John Serrao

I actually enjoyed your piece. I think you're getting piled on.

There are good reasons to use .forEach(), for and while. Just remember, with forEach() it's just an abstraction - it's basically sugar on top of a for loop. This makes it quite a bit slower, but I'd agree syntax is often much cleaner.

Maybe unintentionally, you're veering into a larger discussion on performance and picking the right data structure to use in a given situation. You might want to follow this piece up with a part 2 where you analyze those further.

Collapse
 
blindfish3 profile image
Ben Calder

Agreed on most counts. You don't mention that for loops also allow you to break out of the loop early; which depending on context can provide performance benefits.

There is very little good usage for reduce.

On the contrary, there are some incredibly useful applications for reduce and it can often be used when a normal for loop seems like the only option.

Collapse
 
lluismf profile image
Lluís Josep Martínez

You can't use BREAK or CONTINUE in a foreach/filter/map etc. Normal for loops are there for a reason.

Collapse
 
jackshen profile image
Jack Shen

You shouldn't really stop using for loop, they are more flexible as they are more generalized and in some cases more readable than the prototype functions.

My point is that for loops shouldn't be the first solution, but rather used on a "as needed" basis. I acknowledge in my article there are circumstances where they may be preferable.

There is very little good usage for reduce.

Could you justify this statement?