# Ditch for loops. Here is a case study to convince you

## For loops are for machines

During my career, I've seen some convoluted codes, and spent debugging countless hours. A good portion of it was spent trying to figure out `for` loops. Since other forms of collection processing became mainstream, I hardly ever write loops anymore. And I came to the conclusion that apart from a few special cases, loops are an antipattern.

In this post, I'll iteratively build up a solution with both a `for` loop and a collection pipeline. The specification is somewhat contrived, but it sheds light how loops become unmaintainable over time.

### Collection processing functions

We'll use three of them:

• `map`: transforms each element and returns an Array with the results

It's signature is `(element, index, array) => newElement`.

• `filter`: keep only the elements that the iteratee returns truthy for

The signature is `(element, index, array) => bool`.

• `reduce`: build up the result by repeatedly applying the iteratee to the partial result and the current element

It's signature is `(accumulator, element, index, array) => newAccumulator`. We'll only use it to sum an array of numbers: `.reduce((a, e) => a + e, 0)`.

### Scenario #1

Given an array of numbers, sum the first 10

The solutions (Try them):

With a `for` loop:

``````let result = 0;
for(let i = 0; i < 10; i++) {
result += array[i];
}
// 55
``````

With collection processing:

``````const result = array
.filter((e, i) => i < 10) // 1, 2, ..., 10
.reduce((a, e) => a + e, 0); // 55
``````

So far so good, a trivial problem, trivial solutions.

### Scenario #2

Given an array of numbers, sum the first 10 primes

We have an `isPrime(n) => bool` function ready.

The solutions (Try them):

`for` loop:

``````let result = 0;
let numPrimes = 0;
for(let i = 0; numPrimes < 10; i++) {
const n = array[i];
if (isPrime(n)) {
result += n;
numPrimes += 1;
}
}
// 129
``````

Collection processing:

``````const result = array
.filter(isPrime) // 2, 3, 5, 7, ...
.filter((e, i) => i < 10) // 2, 3, 5, ..., 29
.reduce((a, e) => a + e, 0); // 129
``````

For a localized change in the specification, the `for` loop solution changed in many places. Also, a new variable was needed, which is one of the worst things that hinders readability.

On the other hand, in the collection processing solution, the change was also localized.

### Scenario #3

Same as before, but multiply by the distance from the previous prime

The solutions (Try them):

`for` loop:

``````let result = 0;
let numPrimes = 0;
let lastPrime = undefined;
for(let i = 0; numPrimes < 10; i++) {
const n = array[i];
if (isPrime(n)) {
result += n * (lastPrime !== undefined ? n - lastPrime : 1);
lastPrime = n;
numPrimes += 1;
}
}
// 471
``````

Collection processing:

``````const result = array
.filter(isPrime) // 2, 3, 5, 7, ...
.map((e, i, a) => i > 0 ? e * (e - a[i - 1]) : e) // 2, 3, 10, 14, 44, ...
.filter((e, i) => i < 10) // 2, 3, 10, 14, ..., 174
.reduce((a, e) => a + e, 0); // 471
``````

Yet again, a new variable was needed.

### Scenario #4

The calculation is the same as before, but sum the last 10 numbers

The solutions (Try them):

`for` loop:

``````let lastPrimes = [];
let lastPrimeValues = [];
for(let n of array) {
if (isPrime(n)) {
lastPrimeValues.push(n * (lastPrimes.length > 0 ? n - lastPrimes[lastPrimes.length - 1] : 1));
if (lastPrimeValues.length > 10) {
lastPrimeValues.shift();
}
lastPrimes.push(n);
if (lastPrimes.length > 10) {
lastPrimes.shift();
}
}
}
let result = 0;
for(let n of lastPrimeValues){
result += n;
}
// 3742
``````

What the ...!

Collection processing:

``````const result = array
.filter(isPrime) // 2, 3, 5, 7, ...
.map((e, i, a) => i > 0 ? e * (e - a[i - 1]) : e) // 2, 3, 10, 14, 44, ...
.filter((e, i, a) => i >= a.length - 10) // 318, 354, ..., 776
.reduce((a, e) => a + e, 0); // 3742
``````

The first solution is convoluted, brittle, and hard to maintain, not to mention that it's also hard to understand.

On the other hand, with collection processing, the steps are clear, easy to reason about, and short.

## Conclusion

`for` loops are fine for simple problems, and in some extremely rare cases (much rarer than you think) when even the last drops of performance are needed. But keep in mind that due to the complexity, these solutions hardly stay performant in the long run.

Write code for humans, and not for computers. Collection processing functions make it easier. Use them, and don't get mired in the chaos `for` loops tend to become.

Loops like the one above can quickly become a project's blind spot, that nobody dares to touch.

Prefer the simple.

November 21, 2017