Benchmarking JavaScript Loops and Methods - Part 1
Michael McShinsky

Benchmarking JavaScript Loops and Methods - Part 1

Breaking down the performance and biases of JavaScript loops and methods when working with various data sets.

June 15, 2020

Part 2 is now available here!

Introducing Loops and Methods

We’re going to take a look at the various loops and methods provided in JavaScript to find out which ones are more efficient for the data you are iterating over. The reason I’ve taken the time to put this together comes from watching the evolution of developers and how they use and form opinions about these various loops and methods.

Everyone starts with your basic for loop. Once a new developers learns this, their minds are blown and life becomes easier. This mind blown experience happens over and over as new methods are introduced. What is interesting, is that as soon as new loop and methods are introduced (while, forEach, map, filter, etc…), the basic for loop is left behind in the dust for a long time. This happens anywhere from the first few weeks to the next few months. It will either take a long time or a specific experience in data manipulation for a developer to comes back and consider the basic for loop again in order to meet their objectives.

For this reason, we are going to see if there is any justification to only using methods such as forEach and map, or if there is any merit in sticking with the tried and true for loop.

Data Types

We’re going to tackle each of these loops and methods to discover their advantages and disadvantages against Primitive and Non-primitive data types. If you need a refresher on these data types, here is a list you generally work with.

Primitives

  1. Numbers
  2. Strings
  3. Booleans
  4. Undefined
  5. Null

Non-Primitive

  1. Objects
  2. Arrays
  3. Functions

Finding a Value in an Array

Our benchmarks will show us how efficient our loops are at retrieving, depending on the return value, the value or the index of the value from the array. The loops we will use are:

  1. for
  2. for…of
  3. forEach
  4. while
  5. do…while
  6. find
  7. findIndex
  8. indexOf
  9. lastIndexOf
  10. includes
  11. map
  12. filter
  13. reduce

Let’s start with a small example that displays each of these loops finding a primitive value a from sample array. Note, we’re going to be a little more verbose than some of the "one liner" functions out there in order to capture more values.

Examples of Primitive Arrays:

let namesArray = ['Abe', 'Beth', 'Cody', 'Daniel'];
let textArray = ['Dog', 'Cat', 'Horse', 'Cow'];
let numbersArray = [1, 2, 3, 4];

Starter Code

// Objectives:
// 1. Find the value 7
// 2. Find the index of 7

const OBJECTIVE_NUMBER = 7;

let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
let foundValue;
let foundIndex = -1;

Let’s give ourselves an example of the kind of code we’ll be using for benchmarking. For a full list of loop and method examples, click here!

"for loop" example

// Using array and variables from base code block above…

for (let index = 0; index < arr.length; index++) {
  const value = arr[index];

  if(value === OBJECTIVE_NUMBER) {
    foundValue = value;
    foundIndex = index;
    break;
  }
};

console.log(foundValue); // expected output: 7;
console.log(foundIndex); // expected output: 6;

Benchmarking the Code

Now that we have a base understanding of each of the loops and the possibilities they bring to the table, we can see how they perform against small and large data sets. We’re going to include map, filter, and reduce, even though they are being used in an anti-pattern fashion in order to demonstrate performance across the board. We’ll also benchmark our iterations at finding the value near the start and end of the array for every loop and method. We’ll also test them on different browsers to measure the performance of each browser’s JavaScript engines (Chakra, V8, and SpiderMonkey) which are iterating and optimizing our loops in the background.

Arrays we will be using:

  1. Array 1: 100 primitive values;

  2. Array 2: 1,000 primitive values;

  3. Array 3: 10,000 primitive values;

Note: In Part 2, we’ll look at the same loops but against non-primitives (objects, arrays, functions) and measure performance against them.

Finalized Results

Before we talk about the following results, please remember that there will be variances on performance depending on the hardware and software per user. As a result of this, we as developers should plan for the worst case scenarios in order to provide an optimized experience to users across all platforms and devices. With that in mind, let’s take a look at how our loops performed when searching for a primitive value inside an array.

Note: The graphs represent each loop or method and how many operations per second (op/s) are ran within a given timeframe.

Chrome

Chrome — total operations per second (op/s) to find value within the array

Edge

Edge — total operations per second (op/s) to find value within the array

Firefox

Firefox— total operations per second (op/s) to find value within the array

Breaking Down the Results

After looking at the charts, we can make a few general conclusions:

  1. As data sets get larger, map, reduce, and filter perform the worst when used against their intended purpose or definition.

  2. Against small arrays, Firefox’s engine (SpiderMonkey) is optimized across all methods to iterate over arrays and find values both at the start and end of said arrays.

  3. lastIndexOf performs as expected. It is worse when searching the start of an array and the best when searching for end values. Since this is expected, we will remove this method when comparing for overall performance.

Small Sized Arrays

Let’s start with small arrays for some overall takeaways.

  1. Edge: forEach, map, and reduce perform the best.

  2. Chrome: forEach, map, and reduce perform the best.

  3. Firefox: all methods except map, filter and reduce perform well, but not by much.

  4. Overall Performer: forEach

Medium Sized Arrays

We notice next, that with medium sized arrays and especially when looking for values near the end of the array, performance starts to shift a lot across all the loops and methods.

  1. Edge: indexOf and includes perform better followed by while, do…while, for, and for…of.

  2. Chrome: indexOf and includes take the cake for performance followed by for, while and do…while.

  3. Firefox: A higher performance is recorded here than in Edge and Chrome. for, while, indexOf, and includes are all high performers.

  4. Overall Performers: indexOf and while, since we’re are generally looking from the front to back for our value.

Large Sized Arrays

Finally, we see a much higher drop off in performance as our array grows in size.

  1. Edge: for, while, and indexOf perform the best. Most other loops and methods end up doing poorly.

  2. Chrome: for, while, indexOf and includes stay on top while yet again, we see most other methods failing to perform at the same level.

  3. Firefox: for, while, and indexOf once again are top contenders with the same drop off seen with most of the remaining loops and methods.

  4. Overall Performers: for and while.

Conclusion

Hopefully, as a result of seeing the data, we can all make better decisions on the methods we want to use against various data sets. If we are working with data that may grow over time and we have to iterate over all that data, it may be appropriate to come back to the reliant for loop that has always been there for us. Especially since you can take advantage of it's inherit ability to stop to loop with break and return once you have finished your intended action. While it may not look pretty, it will always be handy.

In Part 2, we will show similar data, but introduce searching non-primitives to see how the results change. This should be even more relevant to daily tasks since a lot of the data we work with comes back as JSON arrays full of objects from a database.