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)
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.
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.Also for loop is significantly faster and more readable in most cases, if you know how to do it correctly.
Note for beginners: If you want to get good, try to understand what's going on under the hood.
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.
for(;;){}
For loop for performance wise.
All of us who appreciate non-clickbait should make our own Wikipedia-like platform where any inaccurate or clickbait articles are immediately deleted.
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?
You're right, dev.to is much better than medium. My comment here added no value to the discussion, apologies to OP.
I actually enjoyed your piece. I think you're getting piled on.
There are good reasons to use
.forEach()
,for
andwhile
. Just remember, withforEach()
it's just an abstraction - it's basically sugar on top of afor
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.
Stop saying things like this
This must be satire.
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
, andforEach
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:
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!