07 — Loops and Iteration — Five Constructs, One Mental Model
"A few flavors of for" was my loop mental model, and it made each flavor feel interchangeable. The idea that everything else hangs off: each loop construct answers a different question, and picking the wrong question is the bug — most often, reaching for for...in on an array when I meant for...of. [1]
The framing that finally landed is matching the construct to the question I'm actually asking:
Count known: the classic for
The plain for loop is for when I know the iteration count up front — a counter, a condition, and an increment, all in the header [2]:
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}It's the most flexible construct (I control the index, the step, the direction), but in modern code I reach for it only when for...of or an array method can't express what I need — say, iterating two arrays in parallel by index.
Count unknown: while and do...while
When the number of iterations isn't known in advance — reading from a stream until empty, retrying until success — while is the right shape [3]. The condition is checked before each iteration, so the body may run zero times:
while (queue.length > 0) {
process(queue.shift());
}do...while is the variant that checks the condition after the body, guaranteeing at least one run [4]. It's the right choice when the first iteration must always happen — prompt-then-validate loops, menu renderers.
Values of an iterable: for...of
for...of is the loop I write most. It iterates over the values of any iterable — arrays, strings, Maps, Sets, TypedArrays, generators, DOM NodeLists [5]:
for (const item of items) { /* item is a value */ }
for (const [key, value] of map) { /* destructure entries */ }It's clean, it's safe (no index to go out of bounds), and it works uniformly across every collection that matters. The rule of thumb: if I'm walking the values of a collection, this is the default.
Keys of an object: for...in
for...in is the one with the trap. It iterates over the enumerable property names (keys) of an object, including inherited ones, and it does not guarantee order [6]. That makes it right for one job — walking an object's keys — and wrong for almost everything else:
const obj = { a: 1, b: 2 };
for (const key in obj) {
console.log(key, obj[key]); // "a" 1, "b" 2
}The classic mistake is using for...in on an array. It technically works (array indices are enumerable string properties), but it also picks up any extra properties added to the array, the order isn't guaranteed, and it's slower. For arrays, it's always for...of or an array method.
The modern alternative to for...in on a plain object is Object.keys(obj), Object.entries(obj), or Object.values(obj) — they return an array I can for...of over, with predictable order and no inherited-property surprises.
break and continue
Two flow-control statements work inside any loop [7]:
- break — exit the loop entirely, skipping remaining iterations.
- continue — skip the rest of this iteration, jump to the next.
Both can take a label to control an outer loop from inside a nested one (outer: for (...) { for (...) { break outer; } }), though labeled breaks are rare in practice — refactoring usually reads better.
Array methods as the hidden loop
The thing I have to remind myself: half the loops I used to write don't need a loop statement at all. map, filter, reduce, find, some, every, forEach are all iteration — they just express what to do with each element rather than how to walk them. When the body is a pure transformation, an array method reads cleaner than a for and avoids index bugs. I reserve explicit loops for stateful iteration (early break, side-effect accumulation) where a method doesn't fit.
How I use this
The decision is now reflexive. Walking values of an array, string, Map, or Set → for...of. Walking keys of a plain object → Object.keys() + for...of, or for...in if I have a reason. Count known → for. Count unknown, body may run zero times → while. Count unknown, body must run at least once → do...while. Pure transformation → an array method, no loop keyword at all. And for...in on an array is a smell I flag in review every time.
References
[1] Mozilla, "Loops and iteration," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Loops_and_iteration
[2] Mozilla, "for statement," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for
[3] Mozilla, "while statement," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/while
[4] Mozilla, "do...while statement," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/do...while
[5] Mozilla, "for...of statement," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of
[6] Mozilla, "for...in statement," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in
[7] Mozilla, "break and continue statements," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/break
Knowledge check · Question 1 of 5
Which loop iterates over the VALUES of an array, string, Map, or Set?
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!