duhan
Blog

Fastest Loop in JavaScript

In this post, I wanted to talk about how to make our app faster with the right loop. In real-world applications, arrays are so important that you end up iterating over them. Are you using the right loop? Probably you could write that loop better. Since we know access time to an array is O(1), they are one of the most efficient data structure for storing and accessing data. I believe that’s why this experiment is important.

One can argue that our machines are so powerful that the loop method we use doesn’t even matter. Most of the time, yes, it doesn’t matter. But not everyone has fast internet access or a powerful device. Also, if this code is going to run on the server, you can use your resources better.

The Experiment

In JS, we have 5 common loops. These are:

  • for of
  • for in
  • for
  • forEach
  • while

I am going to do this experiment with 1.000.000 numbers and 1000 iterations for each loop. I have tried with two different runtimes, Bun and Node. Spoiler, there is a surprize about Bun.

The Output

The measurement contains three key statistical tools to show the performance data:

Average Execution Time: The mean duration, offering a broad view of performance.

Median Execution Time: The middle value in our dataset, providing insight into the typical performance we might expect, unaffected by outliers.

Standard Deviation: A measure of variability, indicating the consistency of each loop type’s execution time.

These tools were chosen to offer a comprehensive analysis, allowing us to understand not just how fast each loop can execute on average, but also how reliable those execution times are across multiple runs.

The Code

The main logic is to get all the data at once. Because I don’t want to cause any modification by calling each loop separately. It looks like this:

measureLoopType("for-of");
measureLoopType("for");
measureLoopType("for-in");
measureLoopType("forEach");
measureLoopType("while");

I haven’t used any 3rd party packages to do this experiment. I have a function named getStats. It just returns the statistics for the given input.

const getStats = (totalDurations) => {
  // Calculate average
  const averageDuration =
    totalDurations.reduce((acc, curr) => acc + curr, 0) / totalDurations.length;

  // Calculate median
  const sortedDurations = [...totalDurations].sort((a, b) => a - b);
  const mid = Math.floor(sortedDurations.length / 2);
  const median =
    sortedDurations.length % 2 !== 0
      ? sortedDurations[mid]
      : (sortedDurations[mid - 1] + sortedDurations[mid]) / 2;

  // Calculate standard deviation
  const mean = averageDuration;
  const squareDiffs = totalDurations.map((value) => (value - mean) ** 2);
  const avgSquareDiff =
    squareDiffs.reduce((acc, curr) => acc + curr, 0) / squareDiffs.length;
  const standardDeviation = Math.sqrt(avgSquareDiff);

  return {
    averageDuration,
    median,
    standardDeviation,
  };
};

I give the input as a number array, and there is a Map that contains the outputs.

const numbers = Array.from({ length: 1_000_000 }, (_, index) => index + 1);
const runs = 1000;
const totalDurations = new Map();

Let’s look into the measurement part. I have a function that I mentioned earlier, measureLoopType. This function gets 1 parameter, which is the loop type. It looks like this:

const measureLoopType = (type) => {
  // code here
};

Now, I need to use 2 loops here. One for the outer iteration and one for the inner iteration (our loop test). We need to measure our loops on that outer iteration. Let’s add outer iteration logic.

But before that remember, our totalDurations variable is a Map, which means we can store our duration (in ms) results like this:

{
  "for-of": [0.01, 0.01124125, 0.05153121],
  "for-in": [0.05, 0.812412, 0.812124]
}

Then our function is:

const measureLoopType = (type) => {
  for (let i = 0; i < runs; i++) {
    const startTime = performance.now();
    // our loops go here...
    const endTime = performance.now();
    const duration = endTime - startTime;

    if (totalDurations.has(type)) {
      totalDurations.get(type).push(duration);
    } else {
      totalDurations.set(type, [duration]);
    }
  }
};

Now, we are able to perform operations. Let’s keep things simple and continue with switch-cases such as:

switch (type) {
  case "for-of":
    for (const number of numbers) {
      // Operation
    }
    break;

  case "for":
    for (let i = 0; i < numbers.length; i++) {
      // Operation
    }
    break;
  // this part can extend...
}

We are not going to make any operations though, we are just iterating the data.

Finally, we can show the results. getStats function returns an object that contains our measurement for the given input. We can grab it like this:

let results = [];

totalDurations.forEach((value, key) => {
  const stats = getStats(key, value);
  results.push({
    LoopType: key,
    Average: `${stats.averageDuration.toFixed(4)} ms`,
    Median: `${stats.median.toFixed(4)} ms`,
    StdDev: `${stats.standardDeviation.toFixed(4)} ms`,
  });
});

console.table(results);

Results

Bun Result:

Bun result

Let’s talk about Bun first. It’s a different story here. The forEach loop was the fastest. It took about 0.6244 ms on average to finish a task, which is very quick. It also had a very small standard deviation, which means it was not only fast but also stable each time the test was run. The other ways to loop, like for and while, were also quick, but not as quick as forEach. The for-in loop was much slower compared to the others. So, if you need to work fast with a big list, forEach is probably the best to use in Bun. This was the surprize.


Node Result: Node result

In Nodejs, the while loop was the fastest way to go through a big list of numbers. It had the lowest median time of 0.3099 ms, which means it was usually the quickest. The for loop was just as quick in median time. The forEach loop was a bit slower this time. The ‘for-in’ loop was much slower than all the other loops. Also, the while loop had a small standard deviation of 0.1874 ms, making it not only fast but also reliable every time it ran. So, in Node.js, using a while or for loop might be the best choice for quick and steady work with big lists. This was the expected result.

GitHub: Fastest Loop in JS


Conclusion

What we see is that in Bun, forEach is not at the end of the list. This is not what we used to see and it’s really a surprise. It shows how different environments can affect the performance of something as simple as loops. We should keep in mind these results when we decide which loop to use. Not to mention, if we are writing server-side code, choosing the right loop can help us use our resources in a better way.