JavaScript Loops Table
| Loop | Best for | Field note |
|---|---|---|
for (let i = 0; i < n; i++) | Counted iterations with the index | The only classic loop that names its own index |
for...of | Values of an array/string/Map | The everyday array loop; break and continue work |
for...in | Object keys | Walks enumerable string keys - never use it on arrays |
while (cond) | Repeat until a condition flips | Check happens first - may run zero times |
do...while (cond) | Run at least once, then check | Menus and retry prompts |
array.forEach(fn) | Side effects per element | Cannot break or await properly - it is a method, not a loop |
array.map(fn) | Transform to a new array | Returns; forEach returns undefined - the classic mixup |
break / continue | Exit early / skip one | Labels let them target outer loops - rarely worth the readability |
JavaScript offers more ways to repeat work than any language needs, and each survives because it answers a different question. The table below is the working eight: the counted loop, the two for...variants people constantly swap, the while family, and the array methods that replaced half of all loops.
Bottom line: for...of walks values and is the correct array loop; for...in walks object KEYS - including inherited ones - and was never meant for arrays. The modern division of labor: for when you need the index or can break early, for...of for everything else iterable, for...in (guarded) for plain objects, and map/filter/reduce when the loop is really building a result.
The honest part: the forEach-or-map confusion is a design smell detector. forEach returns undefined and cannot break; map returns a new array and exists precisely because the callback produces values. A forEach whose callback returns something is a map in disguise - and an async callback inside either does not wait, which is why sequential async work wants for...of with await, and parallel work wants Promise.all.
How to use
- Pick by question: how many times (for), over what values (for...of), over what keys (for...in), until what condition (while).
- Check whether the loop builds a result - if yes, the array methods column is the better answer than any loop.
- For async work: for...of with await runs steps in order; collect promises and Promise.all them when order does not matter.
Frequently asked questions
What is the difference between for...in and for...of?
for...in iterates enumerable string KEYS - property names for an object, and string indexes for an array, plus anything inherited from the prototype chain, which is why looping an array with for...in produces indexes as strings and occasionally extra keys. for...of iterates VALUES of anything iterable - arrays, strings, Maps, Sets - with no inherited surprises. The rule: for...in for plain objects (with a hasOwnProperty or Object.keys guard), for...of for everything else; never for...in on arrays.
When should I use forEach versus map?
forEach executes a function per element and returns undefined - it is for side effects: logging, pushing to another structure, updating the DOM. map transforms each element and returns a new array of the same length. The tells you picked wrong: a forEach followed by reading a variable it filled is usually a map; a map whose return value is ignored is usually a forEach. Neither can break early - for that, use for...of, or the shorter-circuiting methods some/every/find.
How do I loop with await in JavaScript?
for...of with await inside an async function runs the iterations in order, each waiting for the previous - correct for rate-limited calls and dependent steps. Awaiting inside forEach does NOT wait: forEach does not understand promises, so the loop finishes while the work is still running. To run independent awaits in parallel, start them all, collect the promises, and await Promise.all - sequential when order matters, parallel when it does not, and never an await inside forEach.
Why does my for...in loop include properties I never added?
for...in walks the entire prototype chain's enumerable properties - objects inherit from Object.prototype, arrays from Array.prototype, and anything a library added shows up too. The guards: Object.hasOwn(obj, key) inside the loop, or better, Object.keys(obj) / Object.entries(obj) which list only own properties and pair naturally with for...of. Arrays double the problem - indexes arrive as strings, in implementation-defined order for sparse arrays - which is why array iteration belongs to for...of and the array methods.