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
forsimply runs the loop body. map,filter,slice,concatalso allocate a new array, which means new memory and more work for the GC.- A bare
foris predictable, so the JIT compiler (V8, SpiderMonkey) optimises it more easily. - On a million elements
forcan 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
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:
map: 30-60 ms
for: 10-20 msWhat happens under the hood
When you write:
arr.map(x => x * 2);the engine does roughly the following:
- Checks that
arrreally is an array. - Creates a new array for the results.
- On every iteration:
- calls the callback function;
- passes it three arguments
(element, index, array); - stores the result in the new array.
- 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
| Reason | What happens |
|---|---|
| Callback function | A function is called on every iteration, so there are extra call stack frames |
| Allocating a new array | map, filter, slice, concat create copies, which costs extra memory |
| Checks and context | The method runs validations: length, holes, prototype, type |
| Unoptimised closures | If the callback captures outer variables, the overhead grows further |
| Functional principles | These methods are pure and do not mutate the data, so more allocations are needed |
| Loops are easier for the JIT to optimise | The 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
forandmapis 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.
{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:
for (let i = 0; i < n; i++) { /* ... */ }What is actually faster, in order
| Loop | Speed | Notes |
|---|---|---|
for (let i = 0; i < n; i++) | Fastest | No checks, inlined, predictable |
for...of | Fast, but uses an iterator | Some overhead |
while | Roughly the same as for | Depends on the engine |
forEach() | Slower (callback) | Does not return a new array |
map() | Slower | Allocates a new array |
filter(), reduce() | Slower still | Allocations, extra operations |
How to speed up array methods
| Method | Optimisation |
|---|---|
.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:
| Reason | Why it is slower |
|---|---|
| Callback functions | Create extra calls and context |
| A new array | New memory is allocated |
| Validations and iteration | Built in checks and protocols |
| GC pressure | Temporary objects are created |
| Loops are simpler | Easier 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
forloops "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 singlereducedoes the same job in one pass. - Confusing
forEachandforwhen you need to exit early. You cannotbreakout offorEachorreturnfrom the outer function; working around it withthrowcosts more than a plain loop. - Assuming
for...ofis as fast as a classicfor. 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 thanpush()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
maptoforswap.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.