Why Array.forEach Exists When We Have for...of
Javascript Edition
January 20, 2026
Took me longer than I’d like to admit to understand why JavaScript has both forEach and for...of. They both loop through arrays. They look similar. Why have both?
Turns out they’re actually pretty different once you look closer.
The Thing That Confused Me
When I first learned JavaScript, I saw code like this:
array.forEach(item => {\n console.log(item);\n});
And thought “okay, that’s how you loop through arrays.”
Then I saw this:
for (const item of array) {\n console.log(item);\n}
And thought “wait, that also loops through arrays. Are these the same thing?”
They’re not. Not even close.
The Break Problem
First big difference: you can’t break out of forEach.
const numbers = [1, 2, 3, 4, 5];\n\nnumbers.forEach(num => {\n if (num === 3) {\n break; // SyntaxError: Illegal break statement\n }\n console.log(num);\n});
Doesn’t work. forEach is a function, not a loop statement. break only works in actual loops.
With for...of, it’s fine:
for (const num of numbers) {\n if (num === 3) {\n break; // Works fine\n }\n console.log(num);\n}\n// Logs: 1, 2
Same deal with continue:
numbers.forEach(num => {\n if (num === 3) {\n continue; // SyntaxError: Illegal continue statement\n }\n console.log(num);\n});\n\nfor (const num of numbers) {\n if (num === 3) {\n continue; // Works fine\n }\n console.log(num);\n}\n// Logs: 1, 2, 4, 5
This alone is a pretty big difference. If you need to exit early or skip iterations, forEach isn’t going to work.
The Return Value Thing
forEach always returns undefined. Always. Doesn’t matter what you do inside.
const result = [1, 2, 3].forEach(num => {\n return num * 2;\n});\n\nconsole.log(result); // undefined
That return inside the callback? It just exits that callback function. Doesn’t return from the loop. Doesn’t give you a value. Just... ends that iteration.
First time I ran into this, I was trying to build an array from forEach and couldn’t figure out why it wasn’t working. Spent 20 minutes before realizing forEach doesn’t return anything useful.
If you want to transform an array, that’s what map is for:
const doubled = [1, 2, 3].map(num => num * 2);\nconsole.log(doubled); // [2, 4, 6]
With for...of, there’s no return value either - it’s a loop statement, not an expression. But at least you can build up values manually:
const doubled = [];\nfor (const num of [1, 2, 3]) {\n doubled.push(num * 2);\n}
Not as clean as map, but it works.
The Async/Await Gotcha
This one got me recently. Had an array of IDs, needed to fetch data for each one:
const ids = [1, 2, 3];\n\n// This doesn't work how you think\nids.forEach(async (id) => {\n const data = await fetchData(id);\n console.log(data);\n});\n\nconsole.log('Done'); // Logs immediately, before any fetches complete
The callbacks run, but forEach doesn’t wait for them. It just fires them all off and moves on. You get no control over the async flow.
With for...of, you can actually await:
for (const id of ids) {\n const data = await fetchData(id);\n console.log(data);\n}\n\nconsole.log('Done'); // Only logs after all fetches complete
Runs them sequentially, waits for each one. Much more predictable.
If you want parallel execution with proper error handling, Promise.all is better:
await Promise.all(\n ids.map(async (id) => {\n const data = await fetchData(id);\n console.log(data);\n })\n);
But the point stands: forEach and async/await don’t play well together.
The Performance Thing
Okay, here’s where it gets interesting. In theory, for...of should be slightly slower than a regular for loop or forEach because it’s using iterators under the hood.
In practice? The difference is negligible for most code. Modern JavaScript engines optimize this stuff pretty well.
I ran a quick test on a million-item array:
const arr = Array.from({ length: 1000000 }, (_, i) => i);\n\n// for...of\nconsole.time('for...of');\nfor (const item of arr) {\n // do nothing\n}\nconsole.timeEnd('for...of');\n\n// forEach\nconsole.time('forEach');\narr.forEach(item => {\n // do nothing\n});\nconsole.timeEnd('forEach');\n\n// traditional for\nconsole.time('for');\nfor (let i = 0; i < arr.length; i++) {\n // do nothing\n}\nconsole.timeEnd('for');
Results varied by run, but they were all within a few milliseconds of each other. For most real-world code, this doesn’t matter.
Where it might matter: if you’re doing heavy processing on huge arrays in a tight loop. Then a traditional for loop is slightly faster. But honestly, if you’re at that level of optimization, you probably have bigger problems to solve first.
The Index Problem
Sometimes you need the index. forEach gives it to you:
array.forEach((item, index) => {\n console.log(`Item ${index}: ${item}`);\n});
With for...of, you have to use entries():
for (const [index, item] of array.entries()) {\n console.log(`Item ${index}: ${item}`);\n}
Works, but it’s more verbose. If you need the index, forEach is cleaner.
When I Actually Use Each
I reach for for...of when:
-
I might need to break or continue
-
I’m using async/await
-
I’m working with any iterable (not just arrays - Maps, Sets, strings, etc.)
-
I want standard loop control flow
I use forEach when:
-
I’m doing a simple operation on each item
-
I need the index readily available
-
I’m chaining array methods and want consistency
-
I’m never breaking out early
I use neither when:
-
I’m transforming an array →
map -
I’m filtering →
filter -
I’m reducing to a single value →
reduce -
I need maximum performance → traditional
forloop
The Real Answer
Why does forEach exist when we have for...of?
They serve different purposes. forEach is a method - it’s part of the functional programming style of array manipulation. It fits naturally with map, filter, reduce. It’s for when you want to do something with each item and move on.
for...of is a loop statement - it’s for when you need more control. Early exits, async operations, working with any iterable type.
JavaScript didn’t add for...of to replace forEach. It added it because sometimes you need loop control flow, and forEach can’t give you that.
The Advice I Wish I’d Gotten Earlier
Don’t overthink it. Use whichever reads better for what you’re doing.
Need to break out early? for...of.
Need the index easily? forEach.
Using async/await? for...of.
Just doing something with each item? Either works, pick what looks cleaner.
The performance difference doesn’t matter until you’re optimizing hot paths, and by then you’ll know which one you need.
I spent too long trying to pick the “right” one for every situation. Reality is they’re both fine most of the time. Just pick one and move on.
Thanks for reading Under The Hood! Subscribe for free to receive new posts and support my work.