FORUM Menu

Looping Through an Array

by kirupa   |Β Β  filed underΒ JavaScript 101

When working with arrays, we have primarily been accessing our array elements by manually specifying an index position. For many real world cases, this sort of personal attention will not scale. We'll often be dealing with a large collection of data which will find its way into an array, and the only reasonable way to access all of these array elements is to programmatically loop through them. In this article, we'll look at the handful of approaches we have to pull that off and also look at some interesting details around all of this.

Onwards!

For Loop Approach

The oldest and most popular way to loop through an array is by using a for loop. To use a for loop, what we need is a starting point, ending point, and the amount to increment with each run of our loop. With arrays, we totally have all of that information right at our finger tips:

We know the first item starts with an index position of 0. We know the last item has an index position that is one less than the total length of our array (items.length - 1). Our index positions go up by 1, so we know the amount to increment by each time our loop goes through a cycle. Putting this all together, using a for loop to go through the items in our array will look as follows:

let items = ["Ο€", 3.14, "πŸ₯§", Math.PI];

for (let i = 0; i < items.length; i++) {
  let item = items[i];
  
  console.log(item);
}

// "Ο€"
// 3.14
// "πŸ₯§"
// 3.14159265358979

The variable i represents both our loop's current count and the index position. That makes some of our loop-related bookkeeping convenient. I only call this out because our next two loop approaches make us jump through hoops to give us the index position at the current loop iteration.

The for...of Approach

With the earlierfor loop, there is a bit of extra work we need to do up-front as part of getting our loop to run. For a much simpler and cleaner way of looping through our array items, we have the for...of approach. Below is the for...of loop in action:

let animals = ["🐠", "🐀", "πŸ™", "🐝", "πŸ¦–"];

for (let animal of animals) {
    console.log(animal);
}

// 🐠
// 🐀
// πŸ™
// 🐝
// πŸ¦–

Notice what's going on here. We don't really specify anything as part of getting our loop to run. All we specify is the variable that will refer to the current item (animal) and the array (animals) to iterate through. The rest is automatically taken care of for us. That's nice, right?

This simplicity does come with some minor hurdles. For example, we don't have a direct way to get the index position of the item we are currently looping on. To access the index position and array item as as part of each loop iteration, we have to do a few more things as shown in the following snippet:

let animals = ["🐠", "🐀", "πŸ™", "🐝", "πŸ¦–"];

for (let [index, animal] of animals.entries()) {
    console.log(index + ": " + animal);
}

// 0: 🐠
// 1: 🐀
// 2: πŸ™
// 3: 🐝
// 4: πŸ¦–

Instead of looping over our array directly, we are instead looping over our array's entries that are stored as key and value pairs. The key is going to be the index position, and the value will be the contents of the array item. By relying on a technique known as destructuring, we can access the key and value directly and map it to the index and animal variables within our loop definition.

The forEach Approach

Another way of looping through our array items is by relying on theforEach method. Just like for...of, the initial setup work is minimal. Below is forEach in action:

let animals = ["🐠", "🐀", "πŸ™", "🐝", "πŸ¦–"];

animals.forEach((item) => console.log(item));

// 🐠
// 🐀
// πŸ™
// 🐝
// πŸ¦–

Notice that all we really specified is our array, the forEach method, and a callback function that accepts the current array item as an argument. If we wanted to include the current index position as well, our callback function (very conveniently) accepts the index position as its second argument:

let animals = ["🐠", "🐀", "πŸ™", "🐝", "πŸ¦–"];

animals.forEach((item, index) => console.log(index + ": " + item));

// 0: 🐠
// 1: 🐀
// 2: πŸ™
// 3: 🐝
// 4: πŸ¦–

Purely from a code simplicity point of view, the forEach method is really nice. It has some quirks like missing support for break and continue that we'll talk about later, but it's another solid approach for looping through our arrays using a very compact syntax.

Defining Functions is Still Cool!

In our forEach examples, we specified our callback function inline by using an arrow function. Using arrow functions is entirely optional. If you prefer a more traditional function syntax, we can rewrite the loop as follows:

let animals = ["🐠", "🐀", "πŸ™", "🐝", "πŸ¦–"];

animals.forEach(function (item) {
  console.log(item);
});

To go further, the function body doesn't have to be a part of the forEach call. We can separate the function out even further:

let animals = ["🐠", "🐀", "πŸ™", "🐝", "πŸ¦–"];

animals.forEach(printArrayItem);

function printArrayItem(item) {
  console.log(item);
}

There isn't really a right or more preferred approach here. The arrow function syntax is more concise, modern, and hip. It does some nice things with ensuring the value of this is predictable. The function-based syntax is the OG. Use whichever approach you like.

 

Some Additional Looping Tidbits

In the previous sections, we saw how to loop through our array using the for, for...of, and forEach approaches. What we looked at were the basic use cases that we'll always encounter, but we can't fully round out our understanding of how to loop through arrays without looking at a few more slightly less common cases as well.

Looping in Reverse

The default looping direction is one where we start with the first array item and iterate through each subsequent item until we hit the end:

For a handful of reasons, we may want to loop through our array backwards where we start with the last item and make our way towards the front:

 

With a for loop, we can accomplish this by changing our loop's starting and running conditions:

let names = ["George", "Ringo", "Paul", "John"];

for (let i = names.length - 1; i >= 0; i--) {
  let name = names[i];
  
  console.log(name);
}

// John
// Paul
// Ringo
// George

Notice that the various values we use to tell our for loop how to run have flipped from what they were a few sections ago. Our starting point is the last item, we run as long as our index position is greater than or equal to 0, and we increment by -1 to go backwards with each loop iteration.

With the for...of and forEach looping approaches, the direction of the looping is not something we can easily control. Those details are hidden from us. What we do in this case is a little sneaky. We reverse our array itself. By doing this, even though our loop direction remains unchanged, the items we access are now in reverse order:

Putting all of these words and visuals into code, below is what accessing our array items in reverse for the for...of and forEach approaches look like:

let names = ["George", "Ringo", "Paul", "John"];

let reversedNames = names.slice().reverse();

// Using for...of
for (let [index, name] of reversedNames.entries()) {
  console.log(index + ": " + name);
}

// 0: John
// 1: Paul
// 2: Ringo
// 3: George

// Using forEach
reversedNames.forEach((name, index) => console.log(index + ": " + name));

// 0: John
// 1: Paul
// 2: Ringo
// 3: George

The reversedNames array stores a reversed copy of our original names array. Creating a copy of the names array is optional, but the reverse method modifies the array directly. It is considered a bad practice to modify our original data, so we create a (fast and shallow) copy of our array using slice and then call reverse on that:

let reversedNames = names.slice().reverse();

Once we have our reversed array copy, it is business as usual when using the for...of and forEach approaches to loop through our array items. The end result is that our items will now be accessed in a reverse order via the reversedNames array compared to what they were in the original names array.

Inconsistencies

The for, for...of, and forEach looping approaches have a lot of common functionality. What sometimes gets in the way is when some common looping behaviors you may expect don't exist or work differently than expected. Let's look at a few of those inconsistencies.

Control Statements

It's common to use control statements like break or continue to end our loop midway or skip a loop iteration. Both for and for...of support thebreak and continuecontrol statements like a boss:

let items = ["Ο€", 3.14, "πŸ₯§", Math.PI];

for (let i = 0; i < items.length; i++) {
  let item = items[i];

  // End the loop
  if (item == "πŸ₯§") {
    break;
  }
  
  console.log(item);
}

// Ο€
// 3.14

for (let item of items) {

  // Skip current iteration
  if (item == 3.14) {
    continue;
  }

  console.log(item);
}

// Ο€
// πŸ₯§
// 3.141592653589793

The forEach approach, unlike a boss, does not support them. If you need to use forEach and you looping logic relies on using break and continue, the only solution is to change you looping logic or use for or for...of instead.

Iterating Through A Sparse Array

A sparse array is one of those arrays where there are some gaps with array items not directly adjacent to another array item. The following is one way we can end up with a sparse array:

let sparse = ["bar", "foo", "zorb"];
sparse[5] = "blah";

We create our array with the first three items (index positions 0 through 2) prepopulated. We then add blah to live at index position 5. If we had to visualize this array, this is what we will see:

Notice that index positions 3 and 4 are empty. We might expect the length of our array to be a count of the actual elements with content and be 4, but that's not how this all works. The length will be 6 since there are six array items. That two of the array items are empty does not factor into the length calculation. This has some interesting effects in how our various loop approaches deal with iterating through this array.

Let's start with our for loop:

let sparse = ["bar", "foo", "zorb"];
sparse[5] = ["blah"];

for (let i = 0; i < sparse.length; i++) {
  console.log(i + ": " + sparse[i]);
}

// 0: bar
// 1: foo
// 2: zorb
// 3: undefined
// 4: undefined
// 5: blah

The for loop goes through every item including the empty items at index positions 3 and 4. This is similar to behavior our for...of loop shows in this situation as well:

let sparse = ["bar", "foo", "zorb"];
sparse[5] = ["blah"];

for (let [index, item] of sparse.entries()) {
  console.log(index + ": " + item);
}

// 0: bar
// 1: foo
// 2: zorb
// 3: undefined
// 4: undefined
// 5: blah

Where things are different is with forEach:

let sparse = ["bar", "foo", "zorb"];
sparse[5] = ["blah"];

sparse.forEach((item, index) => console.log(index + ": " + item));

// 0: bar
// 1: foo
// 2: zorb
// 5: blah

When iterating through a sparse array using forEach, the callback function only gets called on the array items with actual elements in them. The empty elements are skipped.

Conclusion

Looping through arrays is one of those things that you'll end up doing very frequently. Whether you are traversing the DOM, working with some specialized data structure, handling a web request, or just plain old dealing with a list of data, one of the looping approaches we looked at here is one you will probably end up using. All three of the looping approaches share a lot in common, so unless you need some capability that is more unique, you can't go wrong randomly picking one from a hat and going with it. If I had to pick, my preference is to always use a for loop, mostly because I am very familiar with it. That is closely followed by for...of with forEach being a distant third.

Got a question or just want to chat? Comment below or drop by our forums (they are actually the same thing!) where a bunch of the friendliest people you'll ever run into will be happy to help you out!

When Kirupa isn’t busy writing about himself in 3rd person, he is practicing social distancing…even on his Twitter, Facebook, and LinkedIn profiles.

Hit Subscribe to get cool tips, tricks, selfies, and more personally hand-delivered to your inbox.

COMMENTS

Serving you freshly baked content since 1998!
Killer hosting by (mt) mediatemple

Twitter Youtube Facebook Pinterest Instagram Github