JavaScript Without Loops

Written by James Sinclair
on the 10th February 2017

In the previous article, we suggested that indentation is an (extremely rough) indicator of complexity. Our goal is to write less complex JavaScript. We do this by choosing the right abstraction to solve a problem. But how do you know which abstraction to use? So far, we haven’t looked at any concrete examples of how to do this. In this article we look at how to deal with JavaScript arrays, without using any loops. The end result is less complex code.

“…a loop is an imperative control structure that’s hard to reuse and difficult to plug in to other operations. In addition, it implies code that’s constantly changing or mutating in response to new iterations.”

Note that to keep track of where we’re up to, we use a counter, i. We have to initialise this counter to zero, and increment it every time around the loop. We also have to keep comparing i to len so we know where to stop. This pattern is so common that JavaScript provides a simpler way of writing it: The for-loop. It looks something like this:

This is a helpful construct because it puts all that counter boilerplate together at the top. With the while-loop version it is very easy to forget to increment i and cause an infinite loop. A definite improvement. But, let’s step back a bit and look at what this code is trying to achieve. What we’re trying to do is to run oodlify() on each item in the array and push the result into a new array. We don’t really care about the counter.

This pattern of doing something with every item in an array is quite common. So, with ES2015, we now have a new loop construct that lets us forget about the counter: The for…of loop. Each time around the loop it just gives you the next item in the array. It looks like this:

This is much cleaner. Notice that the counter and the comparison are all gone. We don’t even have to pull the item out of the array. The for…of loop does all that heavy lifting for us. If we stopped here and used for…of loops everywhere instead of for-loops, we’d be doing well. We would have removed a decent amount of complexity. But… we can go further.

Mapping

The for…of loop is much cleaner than the for-loop, but we still have a lot of setup code there. We have to initialise the output array and call push() each time around the loop. We can make our code even more concise and expressive, but to see how, let’s expand the problem a little.

Those two functions are scarily similar. What if we could abstract out the pattern here? What we want is: Given an array and a function, map each item from the array into a new array. Do this by applying the function to each item. We call this pattern map. A map function for arrays looks like this:

The recursive solution is quite elegant. Just two lines of code, and very little indentation. But generally, we don’t tend to use the recursive version because it has bad performance characteristics in older browsers. And in fact, we don’t have to write map ourselves at all (unless we want to). This map business is such a common pattern that JavaScript provides a built-in map method for us. Using this map method, our code now looks like this:

Note the lack of indenting. Note the lack of loops. Sure, there might be a loop going on somewhere, but that’s not our concern any more. This code is now both concise and expressive. It is also simple.

Why is this code simple? That may seem like a stupid question, but think about it. Is it simple because it’s short? No. Just because code is concise, doesn’t mean it lacks complexity. It is simple because we have separated concerns. We have two functions that deal with strings: oodlify and izzlify. Those functions don’t have to know anything about arrays or looping. We have another function, map that deals with arrays. But it doesn’t care what type of data is in the array, or even what you want to do with the data. It just executes whatever function we pass it. Instead of mixing everything in together, we’ve separated string processing from array processing. That is why we can call this code simple.

Reducing

Now, map is very handy, but it doesn’t cover every kind of loop we might need. It’s only useful if you want to create an array of exactly the same length as the input. But what if we wanted to add up an array of numbers? Or find the shortest string in a list? Sometimes we want to process an array and reduce it down to just one value.

All things considered, this code isn’t too bad. We go around the loop, keeping track of the strongest hero so far in strongest. To see the pattern though, let’s imagine we also wanted to find the combined strength of all the heroes.

In both examples we have a working variable that we initialise before starting the loop. Then, each time around the loop we process a single item from the array and update the working variable. To make the loop pattern even clearer, we’ll factor out the inner part of the loops into functions. We’ll also rename the variables to further highlight similarities.

Written this way, the two loops look very similar. The only thing that really changes between the two is the function called and the initial value. Both reduce the array down to a single value. So we’ll create a reduce function to encapsulate this pattern.

Now, as with map, the reduce pattern is so common that JavaScript provides it as a built-in method for arrays. So we don’t need to write our own if we don’t want to. Using the built-in method, our code becomes:

Now, if you’re paying close attention, you may have noticed that this code is not much shorter. Using the built-in array methods, we only save about one line. If we use our hand-written reduce function, then the code is longer. But, our aim is to reduce complexity, not write shorter code. So, have we reduced complexity? I would argue, yes. We have separated the code for looping from the code that processes individual items. The code is less intertwined. Less complex.

The reduce function might seem fairly primitive at first glance. Most examples with reduce do fairly simple things like adding numbers. But there’s nothing saying that the return value for reduce has to be a primitive type. It can be an object, or even another array. This blew my mind a little bit when I first realised it. So we can, for example, write map or filter using reduce. But I’ll leave you to try that out for yourself.

Filtering

We have map to do something with every item in an array. And we have reduce to reduce an array down to a single value. But what if we wanted to extract just some of the items in an array? To explore further, we’ll expand our hero database to include some extra data:

All things considered, this code isn’t too bad. But we definitely have a repeated pattern. In fact, the only thing that really changes is our if-statement. So what if we factored just the if-statements into functions?

Why is this any better than writing the for…of loop? Well, think about how we’d use this in practice. We have a problem of the form Find all the heroes that…. Once we notice we can solve this problem using filter then our job becomes easier. All we need to do is tell filter which items to keep. We do this by writing one very small function. We forget about arrays and working variables. Instead, we write a teeny, tiny predicate function. That’s it.

And as with our other iterators, using filter conveys more information in less space. We don’t have to read through all the generic loop code to work out that we’re filtering. Instead, it’s written right there in the method call.

Finding

Filtering is very handy. But what if we wanted to find just one hero? Say we wanted to Black Widow. We could use filter to find her, like so:

The trouble with this is that it’s not very efficient. The filter method looks at every single item in the array. But we know that there’s only one Black Widow and we can stop looking after we’ve found her. But having this approach of using a predicate function is neat. So let’s write a find function that will return the first item that matches:

And again, JavaScript provides this one for us, so we don’t have to write it ourselves:

const blackWidow = heroes.find(isBlackWidow);

Once again, we end up expressing more information in less space. By using find our problem of finding a particular entry boils down to just one question: How do we know if we’ve found the thing we want? We don’t have to worry about the details of how the iteration is happening.

Summary

These iteration functions are a great example of why (well-chosen) abstractions are so useful and elegant. Let’s assume we’re using the built-in array methods for everything. In each case we’ve done three things:

Eliminated the loop control structure, so the code is more concise and (arguably) easier to read;

Described the pattern we’re using by using the appropriate method name. That is, map, reduce, filter, or find.

Reduced the problem from processing the whole array to just specifying what we want to do with each item.

Notice that in each case, we’ve broken the problem down into solutions that use small, pure functions.
What’s really mind-blowing though, is that with just these four patterns (though there are others, and I encourage you to learn them), you can eliminate nearly all loops in your JS code. This is because almost every loop we write in JS is processing an array, or building an array, or both. And when we eliminate the loops we (almost always) reduce complexity and produce more maintainable code.

Update upon the 23rd of February 2017

A few people have pointed out that it feels inefficient to loop over the hero list twice in the reduce and filter examples. Using the ES2015 spread operator makes combining the two reducer functions into one quite neat. Here’s how I would refactor to iterate only once over the array: