Array Methods Performance: map vs forEach vs for loop - Which is Fastest?
Comprehensive performance comparison of JavaScript array iteration methods. Learn when to use map, forEach, for loop, and other array methods with real benchmarks and best practices.
Introduction
JavaScript array methods like map(), forEach(), for loops, and for...of are fundamental to modern JavaScript development. But have you ever wondered which one is fastest? The answer might surprise you—and it's not always straightforward.
In this comprehensive guide, we'll:
- Benchmark all major array iteration methods
- Analyze real-world performance differences
- Provide actionable recommendations for your code
- Test everything using Perf Lens for accurate measurements
Whether you're optimizing a hot path in your application or just curious about performance, this guide has you covered.
Understanding Array Iteration Methods
JavaScript offers multiple ways to iterate over arrays. Let's understand each one before diving into performance comparisons.
1. Traditional for Loop
The classic loop that's been around since JavaScript's inception.
const arr = [1, 2, 3, 4, 5]; for (let i = 0; i < arr.length; i++) { console.log(arr[i]); }
Characteristics:
- ✅ Maximum control over iteration
- ✅ Can break or continue early
- ✅ Direct array index access
- ❌ More verbose syntax
2. forEach() Method
Introduced in ES5, provides a functional approach to iteration.
const arr = [1, 2, 3, 4, 5]; arr.forEach((item, index) => { console.log(item); });
Characteristics:
- ✅ Clean, functional syntax
- ✅ Automatic iteration over all elements
- ❌ Cannot break early (must iterate all elements)
- ❌ Cannot return a value from the callback
3. map() Method
Creates a new array by transforming each element.
const arr = [1, 2, 3, 4, 5]; const doubled = arr.map(item => item * 2); // Result: [2, 4, 6, 8, 10]
Characteristics:
- ✅ Returns a new transformed array
- ✅ Immutable (doesn't modify original array)
- ✅ Chainable with other array methods
- ❌ Creates a new array (memory overhead)
4. for...of Loop
Modern ES6 loop that iterates over iterable objects.
const arr = [1, 2, 3, 4, 5]; for (const item of arr) { console.log(item); }
Characteristics:
- ✅ Clean syntax, like
forEachbut with loop control - ✅ Can use
breakandcontinue - ✅ Works with any iterable (arrays, strings, Maps, Sets)
- ❌ Slightly slower than traditional
forloop
5. Other Methods
Quick overview of other array iteration methods:
// filter() - create array with elements passing test const evens = [1, 2, 3, 4, 5].filter(x => x % 2 === 0); // [2, 4] // reduce() - reduce array to single value const sum = [1, 2, 3, 4, 5].reduce((acc, x) => acc + x, 0); // 15 // find() - return first element matching condition const firstEven = [1, 2, 3, 4, 5].find(x => x % 2 === 0); // 2 // some() - test if any element passes condition const hasEven = [1, 2, 3].some(x => x % 2 === 0); // true // every() - test if all elements pass condition const allEven = [2, 4, 6].every(x => x % 2 === 0); // true
Performance Benchmarks
Let's compare the performance of different array methods using real benchmarks. All tests were run using Perf Lens with 1000 iterations for accuracy.
Test Setup
// Test array with 10,000 elements const testArray = Array.from({ length: 10000 }, (_, i) => i); // Simple operation: multiply by 2 const operation = (x) => x * 2;
Benchmark 1: Simple Iteration (No Operation)
Just iterating without performing any operation.
// Test 1: Traditional for loop for (let i = 0; i < testArray.length; i++) { const item = testArray[i]; } // Test 2: forEach testArray.forEach(item => { const _ = item; }); // Test 3: for...of for (const item of testArray) { const _ = item; } // Test 4: while loop let i = 0; while (i < testArray.length) { const item = testArray[i]; i++; }
Results (10,000 elements, 1000 iterations):
| Method | Average Time | Relative Speed |
|---|---|---|
for loop | 0.08ms | 1x (baseline) |
while loop | 0.09ms | 1.1x slower |
for...of | 0.12ms | 1.5x slower |
forEach() | 0.15ms | 1.9x slower |
Key Insight: Traditional for loop is the fastest for simple iteration, but the difference is negligible for most use cases (<0.1ms for 10,000 elements).
Benchmark 2: Array Transformation
Creating a new array with transformed values.
// Test 1: for loop + push const result1 = []; for (let i = 0; i < testArray.length; i++) { result1.push(testArray[i] * 2); } // Test 2: for loop + pre-allocated array const result2 = new Array(testArray.length); for (let i = 0; i < testArray.length; i++) { result2[i] = testArray[i] * 2; } // Test 3: map() const result3 = testArray.map(x => x * 2); // Test 4: forEach + push const result4 = []; testArray.forEach(x => { result4.push(x * 2); });
Results (10,000 elements, 1000 iterations):
| Method | Average Time | Memory Delta | Relative Speed |
|---|---|---|---|
for + pre-allocated | 0.12ms | +160 KB | 1x (baseline) |
map() | 0.18ms | +160 KB | 1.5x slower |
for + push | 0.20ms | +160 KB | 1.7x slower |
forEach + push | 0.25ms | +160 KB | 2.1x slower |
Best Practice: For array transformations, map() is only 1.5x slower than the fastest method but offers much better readability. The performance difference is usually negligible.
Benchmark 3: Array Filtering
Finding elements that match a condition.
const isEven = x => x % 2 === 0; // Test 1: for loop const result1 = []; for (let i = 0; i < testArray.length; i++) { if (isEven(testArray[i])) { result1.push(testArray[i]); } } // Test 2: filter() const result2 = testArray.filter(isEven); // Test 3: for...of const result3 = []; for (const item of testArray) { if (isEven(item)) { result3.push(item); } } // Test 4: reduce() const result4 = testArray.reduce((acc, x) => { if (isEven(x)) acc.push(x); return acc; }, []);
Results (10,000 elements, 50% match rate, 1000 iterations):
| Method | Average Time | Relative Speed |
|---|---|---|
for loop | 0.15ms | 1x (baseline) |
for...of | 0.18ms | 1.2x slower |
filter() | 0.22ms | 1.5x slower |
reduce() | 0.30ms | 2x slower |
Benchmark 4: Finding an Element
Searching for the first element matching a condition.
const findTarget = x => x === 5000; // Test 1: for loop with break let result1; for (let i = 0; i < testArray.length; i++) { if (findTarget(testArray[i])) { result1 = testArray[i]; break; // Early exit! } } // Test 2: find() const result2 = testArray.find(findTarget); // Test 3: for...of with break let result3; for (const item of testArray) { if (findTarget(item)) { result3 = item; break; } } // Test 4: forEach (cannot break!) let result4; testArray.forEach(item => { if (findTarget(item) && !result4) { result4 = item; // Still iterates all remaining elements! } });
Results (10,000 elements, target at 50% position, 1000 iterations):
| Method | Average Time | Can Early Exit? |
|---|---|---|
for loop | 0.04ms | ✅ Yes |
for...of | 0.05ms | ✅ Yes |
find() | 0.08ms | ✅ Yes |
forEach() | 0.15ms | ❌ No (iterates all) |
Performance Trap: Never use forEach() for finding elements! It cannot break early and will always iterate the entire array, wasting 2-4x more time.
Real-World Performance Analysis
When Performance Matters
Performance differences become significant in these scenarios:
- Large Arrays (>10,000 elements)
- Hot Paths (code executed thousands of times per second)
- Mobile Devices (limited CPU and memory)
- Real-time Applications (games, animations, data visualization)
// Example: Real-time data processing function processDataFrame(data) { // ❌ Bad: forEach for transformation (2x slower) const results = []; data.forEach(item => { results.push(processItem(item)); }); // ✅ Good: map for transformation const results = data.map(processItem); // ✅ Best: for loop if maximum performance needed const results = new Array(data.length); for (let i = 0; i < data.length; i++) { results[i] = processItem(data[i]); } }
When Performance Doesn't Matter
For most applications, readability > micro-optimizations:
// ✅ Perfectly fine for typical use cases const userNames = users.map(user => user.name); const activeUsers = users.filter(user => user.active); const totalScore = scores.reduce((sum, score) => sum + score, 0);
Rule of Thumb:
- Array < 1,000 elements: Use any method you prefer
- Array < 10,000 elements: Performance difference is ~0.1-0.5ms
- Array > 10,000 elements: Consider
forloops for hot paths
Memory Consumption Comparison
Memory usage is often more important than speed.
Memory Benchmark
const testArray = Array.from({ length: 100000 }, (_, i) => ({ id: i, value: Math.random(), data: new Array(10).fill(i) })); // Test 1: map() - creates new array const result1 = testArray.map(item => item.value); // Memory Delta: +800 KB (new array created) // Test 2: forEach with mutation - no new array testArray.forEach(item => { item.processed = true; }); // Memory Delta: +100 KB (added property to existing objects) // Test 3: for loop with new array const result3 = new Array(testArray.length); for (let i = 0; i < testArray.length; i++) { result3[i] = testArray[i].value; } // Memory Delta: +800 KB (pre-allocated array)
Key Findings:
map()always creates a new array (+memory)forEach()with mutation is memory-efficient- Pre-allocating arrays saves time but uses memory
Memory vs Speed Trade-off: map() uses more memory but is often faster than forEach() + push() because the array is pre-allocated internally by V8.
Decision Matrix: Which Method to Use?
Quick Reference Guide
| Use Case | Recommended Method | Reason |
|---|---|---|
| Transform array | map() | Clean syntax, immutable, chainable |
| Filter array | filter() | Purpose-built, clean syntax |
| Find one element | find() or for loop | Early exit, efficient |
| Check if any/all | some() / every() | Purpose-built, early exit |
| Reduce to value | reduce() | Most expressive for aggregation |
| Side effects only | forEach() or for...of | Clear intent |
| Maximum performance | for loop | Fastest, most control |
| Need early exit | for or for...of | Can use break |
Detailed Decision Tree
Loading diagram...
Advanced Optimization Techniques
1. Caching Array Length
// ❌ Slower: length accessed every iteration for (let i = 0; i < array.length; i++) { process(array[i]); } // ✅ Faster: length cached (micro-optimization) const len = array.length; for (let i = 0; i < len; i++) { process(array[i]); }
Impact: ~5-10% faster for very large arrays (>100,000 elements).
Note: Modern JavaScript engines often optimize this automatically. Only use in performance-critical code.
2. Reverse Iteration
// Slightly faster for large arrays for (let i = array.length - 1; i >= 0; i--) { process(array[i]); }
Why faster?
- Comparing to zero is slightly faster than comparing to length
- Saves one subtraction per iteration
Impact: ~3-5% faster, but only use if order doesn't matter.
3. Unrolling Loops
For extremely performance-critical code:
// Standard loop for (let i = 0; i < array.length; i++) { sum += array[i]; } // Unrolled loop (process 4 at a time) const len = array.length; const remainder = len % 4; let i = 0; // Process bulk for (; i < len - remainder; i += 4) { sum += array[i]; sum += array[i + 1]; sum += array[i + 2]; sum += array[i + 3]; } // Process remainder for (; i < len; i++) { sum += array[i]; }
Impact: ~20-30% faster for large arrays with simple operations.
Trade-off: More complex code, harder to maintain. Only use in hot paths.
4. Using TypedArrays
For numeric data, TypedArrays are significantly faster:
// Regular array const arr = new Array(1000000); for (let i = 0; i < arr.length; i++) { arr[i] = i; } // TypedArray (2-3x faster for numeric operations) const typedArr = new Float64Array(1000000); for (let i = 0; i < typedArr.length; i++) { typedArr[i] = i; }
Use cases:
- Graphics/WebGL
- Audio processing
- Large numeric datasets
- Scientific computing
Common Performance Pitfalls
1. Using forEach() When You Need Early Exit
// ❌ Bad: forEach cannot break let found; array.forEach(item => { if (item.id === targetId) { found = item; // Still iterates all remaining elements! } }); // ✅ Good: Use find() or for loop const found = array.find(item => item.id === targetId); // ✅ Better for complex logic let found; for (const item of array) { if (item.id === targetId) { found = item; break; // Stops immediately } }
2. Chaining Multiple Array Methods
const data = [/* 10,000 items */]; // ❌ Bad: Multiple iterations + multiple arrays const result = data .filter(item => item.active) // Iteration 1, new array .map(item => item.value) // Iteration 2, new array .filter(value => value > 10); // Iteration 3, new array // ✅ Better: Single iteration const result = data.reduce((acc, item) => { if (item.active && item.value > 10) { acc.push(item.value); } return acc; }, []); // ✅ Best: for loop for maximum performance const result = []; for (let i = 0; i < data.length; i++) { const item = data[i]; if (item.active && item.value > 10) { result.push(item.value); } }
Impact: Single iteration is 2-3x faster and uses less memory.
3. Not Pre-allocating Arrays
// ❌ Slower: Array grows dynamically const result = []; for (let i = 0; i < 10000; i++) { result.push(i * 2); } // ✅ Faster: Pre-allocated array const result = new Array(10000); for (let i = 0; i < 10000; i++) { result[i] = i * 2; }
Impact: ~15-20% faster for large arrays.
Testing Your Code
Want to verify these results yourself? Use Perf Lens to test array method performance:
Example Test Case
// Test: map vs forEach vs for loop const testArray = Array.from({ length: 10000 }, (_, i) => i); // Version 1: map const result1 = testArray.map(x => x * 2); // Version 2: forEach const result2 = []; testArray.forEach(x => { result2.push(x * 2); }); // Version 3: for loop const result3 = new Array(testArray.length); for (let i = 0; i < testArray.length; i++) { result3[i] = testArray[i] * 2; } // Test with Perf Lens: // 1. Copy each version to Perf Lens // 2. Run performance test (100-1000 iterations) // 3. Compare execution time and memory usage // 4. Export reports and compare side-by-side
Best Practices Summary
For Readability (Most Cases)
// ✅ Use functional methods for clarity const doubled = numbers.map(x => x * 2); const evens = numbers.filter(x => x % 2 === 0); const sum = numbers.reduce((a, b) => a + b, 0); const firstNegative = numbers.find(x => x < 0);
For Performance (Hot Paths)
// ✅ Use for loops in performance-critical code const result = new Array(numbers.length); for (let i = 0; i < numbers.length; i++) { result[i] = numbers[i] * 2; }
For Early Exit
// ✅ Use for loop or for...of with break for (const item of items) { if (condition(item)) { handleItem(item); break; } }
For Side Effects
// ✅ Use forEach or for...of items.forEach(item => { updateDOM(item); logEvent(item); });
Conclusion
Key Takeaways:
-
forloops are fastest (~1.5-2x faster thanforEach), but the absolute difference is small (<0.1ms for 10,000 elements) -
Use
map(),filter(),reduce()for most cases—readability matters more than micro-optimizations -
Avoid
forEach()for finding elements—it cannot break early and wastes performance -
Optimize hot paths—if code runs thousands of times per second, use
forloops -
Pre-allocate arrays—saves 15-20% time for large arrays
-
Test your specific use case—performance varies by browser, data size, and operation complexity
Rule of Thumb:
- Write readable code first (
map,filter,reduce) - Optimize hot paths later (use
forloops) - Always measure before optimizing (use Perf Lens!)
Next Steps
Now that you understand array method performance, explore these related topics:
- JavaScript Memory Leaks: Detection and Prevention - Learn how to avoid memory leaks with arrays
- 10 JavaScript Performance Optimization Techniques - More optimization strategies
- How to Measure JavaScript Performance - Accurate benchmarking techniques
Ready to test your array code? Try Perf Lens - a free online JavaScript performance testing tool with execution time, memory usage, and bundle size analysis.