February 7, 2023

You probably don't need loops

Loops are one of the most basic features of any programming language. They allow us to iterate over some array to perform some operations, like transforming data, filtering out some data, or creating new arrays.

Sometimes, you need to use loops, but most of the times (like 99% of the time) you can just use the collection methods provided by JavaScript, such as map and filter.

They are better because you can perform collection operations in a form of a pipeline, which means one operation hands its result to the next one. This way you can make the code much more readable because you can read and understand each operation separately from the others.

I’ll show you an example below. First, I’ll use loops to achieve the desired results, and then, I’ll refactor it to use collection methods.

The example

In this example, I have an array of strings: each one representing a user record read from some file—like CSV.

const users = [
  'Jane, Doe, 25, Web Developer',
  'John, Doe, 28, Web Developer',
  '',
  'Alex, Smith, 30, Project Manager'
]

The goal here is to pick out web developer users and convert each string record into an object that has these fields: firstName, lastName, age, and role.

In real-world apps, it’s possible that reading some file will return empty strings in some lines; so, I’ve added an empty string in the third element to simulate that.

Implementing the example using loops

If I were to implement this with loops, I’ll write it like this:

const webDevelopers = []

for (const userRecord of users) {
  if (userRecord.trim() === '') {
    continue
  }
  const user = userRecord.split(',')
  if (user[3].trim() !== 'Web Developer') {
    continue
  }
  const userObject = {
    firstName: user[0].trim(),
    lastName: user[1].trim(),
    age: +user[2].trim(),
    role: user[3].trim()
  }
  webDevelopers.push(userObject)
}

Even though it’s a very simple example, the code still feels a little bit messy and not so easy to read. I’m not a big fan of using continue because I have to keep track of what will be skipped and what will be allowed. And the more logic and checks the loop has, the more complicated it will be.

What makes this even worse is the time when you want to modify or add more logic to the loop. If your loop is more complicated (which is the case in most real-world apps), then it will be more difficult to know where to put the new code—you need to be sure that it doesn’t get skipped by some of the previous continues.

Refactoring the example with collection methods

Here’s the same code but using collection methods:

const webDevelopers = users
  .filter((record) => record.trim() !== '')
  .map((record) => record.split(','))
  .filter((record) => record[3].trim() === 'Web Developer')
  .map((record) => ({
    firstName: record[0].trim(),
    lastName: record[1].trim(),
    age: +record[2].trim(),
    role: record[3].trim()
  }))

Look how organized the code is now. We can read it as a series of operations; each operation has a clear goal.

  • The first filter filters out all empty strings.
  • The first map convert each string into an array of values (values that was separated by a comma).
  • The second filter filters out all non–web-developer users.
  • The second map converts each array of values into an object.
Stay up-to-date on the latest projects and articles from me