Optimizing JavaScript Loops for Fast Performance

Small loop optimizations add up when you iterate thousands of times. Learn which patterns actually speed up JavaScript loops and which micro-optimizations do not matter.

6 min read

Loop optimization is about reducing the work done inside each iteration. When a loop runs thousands of times, a small saving per iteration multiplies into a meaningful speed difference.

Most loop performance comes down to two principles: avoid repeating work that only needs to happen once, and keep the loop body as lean as possible.

Cache the Array Length

Every array access through length is a property lookup, and repeating that lookup on every pass through a loop is wasted work once the array size is known. The most impactful optimization is caching the array length before the loop:

javascriptjavascript
const items = ["pen", "notebook", "eraser"];
 
// SLOWER: reads items.length on every iteration
for (let i = 0; i < items.length; i++) {
  console.log(items[i]);
}

In the slower version, JavaScript recalculates items.length before every single iteration. For a small array like this one it makes no visible difference, but for a 10,000-item array, that is 10,000 extra property lookups. Caching the length avoids that repeated work:

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

Both loops print the same three items, but the second version reads the length once and stores it in len instead of checking it on every pass.

You can declare the cached length right in the for loop header using a comma to separate the two let declarations.

Hoist Repeated Property Lookups

If the loop body accesses the same object property or array element multiple times, pull it into a local variable:

javascriptjavascript
const config = { threshold: 100, multiplier: 1.5 };
const data = [80, 150, 95, 200];
 
// SLOWER: property lookup every iteration
for (let i = 0; i < data.length; i++) {
  if (data[i] > config.threshold) {
    console.log(data[i] * config.multiplier);
  }
}

This prints 225 and 300 for the two values above the threshold, but each check reads config.threshold and config.multiplier straight from the object again on every pass. Hoisting those two lookups into local variables first avoids that repeated work:

javascriptjavascript
const threshold = config.threshold;
const multiplier = config.multiplier;
 
for (let i = 0; i < data.length; i++) {
  if (data[i] > threshold) {
    console.log(data[i] * multiplier);
  }
}

The output is identical, but with a 10,000-item array, reading the two values once instead of inside the loop avoids 20,000 property lookups.

Avoid Creating Objects Inside Hot Loops

Object creation allocates memory, and in a tight loop that allocation adds up:

javascriptjavascript
const data = [10, 20, 30];
 
// SLOWER: new object allocated every iteration
const results = [];
for (let i = 0; i < data.length; i++) {
  results.push({ index: i, value: data[i] });
}
console.log(results);
// [{ index: 0, value: 10 }, { index: 1, value: 20 }, { index: 2, value: 30 }]

Each pass allocates a brand new object, which is fine for 3 items but adds up across 10,000 or more. If you only need the values, pushing them directly skips that allocation entirely:

javascriptjavascript
const values = [];
for (let i = 0; i < data.length; i++) {
  values.push(data[i]);
}
console.log(values);
// [10, 20, 30]

If you must collect structured results, consider whether .map() is clearer and whether the object creation is truly the bottleneck. For most non-trivial loops, the work inside the object (computation, string building, DOM access) dwarfs the allocation cost.

Choose the Right Loop Type

Different loop types have slightly different performance characteristics:

javascriptjavascript
const arr = ["x", "y", "z"];
 
// Classic for (fastest for raw iteration)
for (let i = 0, len = arr.length; i < len; i++) {
  console.log(arr[i]);
}

This is the same raw index access you have already seen, and it has the least overhead of the three approaches. The next two styles print the exact same values with cleaner syntax:

javascriptjavascript
// for...of (slightly slower, much cleaner)
for (const item of arr) {
  console.log(item);
}
 
// forEach (slightly slower, functional style)
arr.forEach((item) => {
  console.log(item);
});

All three loops print x, y, z in the same order. The difference between them is not in the output but in how much overhead each one adds per iteration.

Loop type performance comparison

The classic for loop with a cached length is generally the fastest because it avoids the iterator protocol overhead of for...of and the function call overhead of forEach. But the difference is measured in microseconds per iteration. For arrays under a few thousand items, you will not notice it.

Choose clarity first. Optimize only when a profiler tells you the loop is a bottleneck.

Reduce Work Inside the Loop

The biggest performance wins come from removing unnecessary work from the loop body, not from micro-optimizing the loop header:

javascriptjavascript
// SLOWER: recomputes Math.sqrt(2) inside every iteration
const numbers = [1, 2, 3];
for (let i = 0, len = numbers.length; i < len; i++) {
  const result = numbers[i] * Math.sqrt(2);
  console.log(result);
}
// 1.4142135623730951, 2.8284271247461903, 4.242640687119285

Math.sqrt(2) never changes between iterations, so recalculating it each time is wasted work. Moving it outside the loop computes it once and reuses the same value:

javascriptjavascript
// FASTER: compute the constant once, outside the loop
const numbers = [1, 2, 3];
const factor = Math.sqrt(2);
for (let i = 0, len = numbers.length; i < len; i++) {
  const result = numbers[i] * factor;
  console.log(result);
}
// Same output, one sqrt call instead of three

The general rule: if a computation inside the loop does not depend on the loop variable, move it outside.

Measuring Loop Performance

Use console.time() and console.timeEnd() to measure before and after:

javascriptjavascript
const data = Array.from({ length: 100000 }, (_, i) => i);
 
console.time("uncached length");
for (let i = 0; i < data.length; i++) {
  const x = data[i];
}
console.timeEnd("uncached length");

This times a pass over 100,000 items using the uncached version. Running the cached version right after it with the same data lets you compare the two numbers directly:

javascriptjavascript
console.time("cached length");
for (let i = 0, len = data.length; i < len; i++) {
  const x = data[i];
}
console.timeEnd("cached length");

The exact numbers vary by engine and machine, but the cached version is consistently a little faster than the uncached one. Typical output looks like this:

texttext
uncached length: 0.35ms
cached length: 0.28ms

The difference is small for a single pass, but across many loops and larger arrays, caching the length consistently helps.

What Does NOT Matter

These micro-optimizations are not worth the readability cost:

  • Using ++i instead of i++. Modern engines produce identical code for both.
  • Counting down to zero (i-- with a not-equal-to-zero check). Slightly faster on some very old engines, but not worth the confusing code in 2026.
  • Avoiding let in the loop header. The engine optimizes this away.
  • Replacing for...of with a for loop for arrays under 1,000 items. The readability gain of for...of outweighs the microsecond difference.

Optimization Checklist

OptimizationImpactWhen to Apply
Cache array lengthMediumArrays over 1,000 items
Hoist property lookupsMediumRepeated object access in body
Avoid object creation in loopMediumHot loops with 10k+ iterations
Classic for over for...ofLowOnly when profiler confirms bottleneck
Move constant work outside loopHighWhenever the computation is loop-invariant
Use a simpler algorithmHighNested loops that can be replaced with Map/Set

The most impactful optimization is often not about the loop syntax at all. If a nested loop is causing an N-squared slowdown, replacing the inner loop with a Map lookup or a Set for deduplication can turn O(N^2) into O(N) -- a far bigger win than any loop-header tweak.

Rune AI

Rune AI

Key Insights

  • Cache array.length in a variable before the loop to avoid re-reading it each iteration.
  • Hoist repeated property lookups outside the loop body.
  • Classic for loops are generally the fastest, but the difference is small for typical array sizes.
  • Avoid creating objects inside hot loops; reuse references when possible.
  • Measure with console.time() before and after optimizing to confirm the change matters.
RunePowered by Rune AI

Frequently Asked Questions

Which loop type is fastest in JavaScript?

The classic for loop with a cached length is generally the fastest for iterating arrays. But the difference is tiny for most use cases. Choose the loop that makes your code clearest first, and only micro-optimize when a profiler shows the loop is a bottleneck.

Does forEach perform worse than a for loop?

forEach is slightly slower than a classic for loop because it calls a function for each element. For most arrays under a few thousand items, the difference is not noticeable. The bigger limitation is that forEach cannot break or use await.

Should I avoid creating variables inside a loop?

For primitives like numbers and strings, creating variables inside the loop is fine. For objects and arrays, declaring them inside the loop allocates new memory each iteration, which can add up. Declare object references outside the loop and reassign inside when possible.

Conclusion

Loop performance is about avoiding unnecessary work inside each iteration. Cache the array length, hoist repeated property lookups, and keep the loop body lean. Most importantly, measure before optimizing -- a slow operation inside the loop dwarfs any micro-optimization to the loop itself.