Stop using for loops. Here's why.
Tags: javascript, webdev, beginners, tutorial
Published: 2020-03-02 04:48:55

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".
```js
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:
```js
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.

```js
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?
```js
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:
```js
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.

```js
// 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.