Skip to main content

Array methods vs loops

Array methods (map, filter, forEach, reduce and friends) are abstractions over loops, implemented inside the JavaScript engine. They are more convenient, shorter and more readable, but they carry extra overhead, which can make them slower than plain for, for...of and while loops, especially on large arrays.

Theory

TL;DR

  • An array method calls a callback on every iteration and passes it three arguments, while for simply runs the loop body.
  • map, filter, slice, concat also allocate a new array, which means new memory and more work for the GC.
  • A bare for is predictable, so the JIT compiler (V8, SpiderMonkey) optimises it more easily.
  • On a million elements for can be 2 to 5 times faster; on ten thousand the difference is a fraction of a millisecond.
  • In ordinary product code readability beats that difference, so .map() in JSX is perfectly fine.
  • Optimise only hot paths: rendering, parsing, binary data processing, canvas, heavy aggregations.

Quick example

javascript
const arr = Array.from({ length: 1_000_000 }, (_, i) => i); // map console.time('map'); const doubled1 = arr.map(x => x * 2); console.timeEnd('map'); // for console.time('for'); const doubled2 = new Array(arr.length); for (let i = 0; i < arr.length; i++) { doubled2[i] = arr[i] * 2; } console.timeEnd('for');

On most engines the result looks roughly like this:

text
map: 30-60 ms for: 10-20 ms

What happens under the hood

When you write:

javascript
arr.map(x => x * 2);

the engine does roughly the following:

  1. Checks that arr really is an array.
  2. Creates a new array for the results.
  3. On every iteration:
    • calls the callback function;
    • passes it three arguments (element, index, array);
    • stores the result in the new array.
  4. Returns the resulting array.

That is a pile of steps which add overhead compared to a plain for loop, where you just run the instructions with no wrappers and no checks.

Why exactly it is slower

ReasonWhat happens
Callback functionA function is called on every iteration, so there are extra call stack frames
Allocating a new arraymap, filter, slice, concat create copies, which costs extra memory
Checks and contextThe method runs validations: length, holes, prototype, type
Unoptimised closuresIf the callback captures outer variables, the overhead grows further
Functional principlesThese methods are pure and do not mutate the data, so more allocations are needed
Loops are easier for the JIT to optimiseThe engine (V8, SpiderMonkey) optimises a bare for faster

Why it shows up in hot paths

If a loop runs millions of times (rendering, sorting, data aggregation, parsing), callback overhead starts to cost real money.

In those places:

  • every callback call is a new call stack frame;
  • extra allocations mean more work for the GC (garbage collector);
  • the extra arguments (index, array) mean more objects in memory.

When it does not matter

For most business tasks (lists, filters, mapping up to ten thousand elements):

  • the difference between for and map is a fraction of a millisecond;
  • readability and clean code matter more.

That is exactly why React/Vue code uses .map() for JSX: it is declarative and clear.

javascript
{items.map(item => <Card key={item.id} {...item} />)}

But if you have an array of millions of elements, or a loop in a hot spot (rendering, binary data processing, canvas, a parser), prefer:

javascript
for (let i = 0; i < n; i++) { /* ... */ }

What is actually faster, in order

LoopSpeedNotes
for (let i = 0; i < n; i++)FastestNo checks, inlined, predictable
for...ofFast, but uses an iteratorSome overhead
whileRoughly the same as forDepends on the engine
forEach()Slower (callback)Does not return a new array
map()SlowerAllocates a new array
filter(), reduce()Slower stillAllocations, extra operations

How to speed up array methods

MethodOptimisation
.map()Use a pure arrow function with no outer closures
.filter()Do not chain .map().filter().reduce(), merge them into a single pass
.reduce()For complex operations move the accumulation into a for
.forEach()Replace with for or for...of in hot code
.concat() / spread ([...a, ...b])On large arrays replace with push.apply() or a loop

A short summary of the reasons:

ReasonWhy it is slower
Callback functionsCreate extra calls and context
A new arrayNew memory is allocated
Validations and iterationBuilt in checks and protocols
GC pressureTemporary objects are created
Loops are simplerEasier for the JIT compiler to optimise

Conclusion: for performance critical tasks use for, for clear declarative code use map, filter, reduce.

Common mistakes

  • Rewriting all code as for loops "for speed". Without measuring this loses readability with no gain: in 99% of places the difference is invisible.
  • Chaining .map().filter().reduce() on large arrays. Every link walks the whole array and allocates an intermediate one. A single loop or a single reduce does the same job in one pass.
  • Confusing forEach and for when you need to exit early. You cannot break out of forEach or return from the outer function; working around it with throw costs more than a plain loop.
  • Assuming for...of is as fast as a classic for. It goes through the iterator protocol and creates a { value, done } object on every step.
  • Forgetting to preallocate the array. new Array(n) with index assignment is cheaper than push() in a loop over millions of elements.
  • Micro optimising instead of changing the algorithm. Turning O(n squared) into O(n) buys far more than any map to for swap.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.