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.
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:
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:
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:
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:
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:
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:
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:
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:
// 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.
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:
// 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.242640687119285Math.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:
// 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 threeThe 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:
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:
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:
uncached length: 0.35ms
cached length: 0.28msThe 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
++iinstead 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
| Optimization | Impact | When to Apply |
|---|---|---|
| Cache array length | Medium | Arrays over 1,000 items |
| Hoist property lookups | Medium | Repeated object access in body |
| Avoid object creation in loop | Medium | Hot loops with 10k+ iterations |
| Classic for over for...of | Low | Only when profiler confirms bottleneck |
| Move constant work outside loop | High | Whenever the computation is loop-invariant |
| Use a simpler algorithm | High | Nested 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
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.
Frequently Asked Questions
Which loop type is fastest in JavaScript?
Does forEach perform worse than a for loop?
Should I avoid creating variables inside a loop?
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.
More in this topic
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.
How to Loop Through Arrays Using JS for Loops Guide
Looping through arrays is the most practical use of JavaScript for loops. Learn how to access each element by index, avoid off-by-one errors, and choose between for, for...of, and forEach.