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.

Latest comments (37)

Collapse
 
john_malone_97e041014f7a4 profile image
John Malone • Edited

So in summary: if there is a built in function to do what you want to do in your potential loop, then use it. That is, if someone else implemented your ugly for loop for you, then you don't need a for loop. If they did not, then...... what exactly? I have been programming for 20 years and have no idea what this discussion (that I've seen before on other sites) is actually about.

Collapse
 
revenity profile image
Revenity • Edited

One problem with using syntatic sugar like these are people recreating a callback on each call of a function that contains a .map, .forEach or whatever.

function f(a) {
    a.forEach(item => { ... });
}
Enter fullscreen mode Exit fullscreen mode

Also for loop is significantly faster and more readable in most cases, if you know how to do it correctly.

// Not this (Rely on iterator protocol which is slow af)
for (const item of arr) {}

// This (Cache the length pls)
for (let i = 0, { length } = arr; i < length; ++i) {}
Enter fullscreen mode Exit fullscreen mode

Note for beginners: If you want to get good, try to understand what's going on under the hood.

Collapse
 
richfinelli profile image
Rich Finelli

I love this take. for loops are tough to read, albeit .forEach() is only slightly more readable. Most times for loops don't need to be replaced with forEach(), they can be replaced with a .map(), .filter(), etc and be much more readable, avoid mutation and allow your code to follow a more functional pattern. If you think your array methods are slowing down your app, consider swapping out with a traditional for loop, but i wouldn't pre-optimize this way at the expense of readability and maintainability. The key to web apps at scale is a maintainable code base which means its readable and intentions are clear. This allows team members to make changes quickly without fear of bugs.

Chances are that your array methods aren't what's making your app slow. Here are some common reasons your app is slow: netguru.com/blog/11-reasons-why-yo.... But, if you're dealing with a large data set, and the array methods are causing an unwanted delay, switch back to for loop.

Collapse
 
edwardmauthe profile image
EdwardMauthe

for(;;){}

Collapse
 
kimsean profile image
thedevkim

For loop for performance wise.

Collapse
 
jkhaui profile image
Jordy Lee

All of us who appreciate non-clickbait should make our own Wikipedia-like platform where any inaccurate or clickbait articles are immediately deleted.

Collapse
 
chechenev profile image
Maxim Chechenev

Wow, that's so aggressive.

If I want clickbait titles I could open Medium and find hundreds of them. And it's very difficult to find a real useful article there. Do you want Dev.to to be same place?

Thread Thread
 
jkhaui profile image
Jordy Lee

You're right, dev.to is much better than medium. My comment here added no value to the discussion, apologies to OP.

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
 
belhassen07 profile image
Belhassen Chelbi

Stop saying things like this

Collapse
 
lehmannsystems profile image
Mike

This must be satire.

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!