Can you write this from memory?
Write a for loop that iterates `i` from 0 to 4 (inclusive).
Most JavaScript bugs around "loops" aren't about syntax. They're about picking the wrong tool.
Quick decision rules:
- for...of iterates values of iterables (arrays, strings, sets) and supports break/continue
- for...in iterates enumerable string keys (including inherited properties). Use only for objects, with caution
- map/filter/reduce are for pure transformations (no mutation, returns new array)
- find/some/every are your "stop early" tools
- forEach is for side effects only. You cannot break and it doesn't await async callbacks
These drills make your loop choice automatic.
| What you want to do | Best tool | Why |
|---|---|---|
| Transform every item | map | Pure transform, returns new array |
| Filter items | filter | Returns subset matching condition |
| Find first match | find / findIndex | Stops at first match |
| Check if any match | some | Stops early on first true |
| Check if all match | every | Stops early on first false |
| Need break/continue | for / for...of | Array methods can't break |
| Need index access | for or for...of + .entries() | Direct index control |
| Iterate object keys/values | Object.entries() + for...of | Predictable, no inherited props |
| Aggregate to single value | reduce | With initial value |
| Side effects (logging, etc) | forEach | When you don't need result |
| Unknown iteration count | while / do...while | Condition-based |
| Async iteration | for await...of | For async iterables (see async page) |
Performance note: Native loops (for, while) are marginally faster than array methods (map, forEach) in micro-benchmarks, but the difference rarely matters in practice. Choose based on readability and intent. Optimize only if profiling shows a bottleneck. If you're coming from Python, the Python vs JavaScript loops comparison covers the key differences in iteration style.
const arr = ['a', 'b', 'c'];
// for...of iterates VALUES
for (const item of arr) {
console.log(item); // 'a', 'b', 'c'
}
// for...in iterates KEYS (as strings!)
for (const key in arr) {
console.log(key); // '0', '1', '2' (strings, not numbers!)
}
Why for...in is dangerous on arrays
// Add something to Array prototype (libraries sometimes do this)
Array.prototype.customMethod = function() {};
const arr = ['a', 'b', 'c'];
for (const key in arr) {
console.log(key);
}
// Output: '0', '1', '2', 'customMethod' (includes inherited!)
Rule: Use for...of for arrays. Use for...in only for objects when you understand the implications.
for...in for objects (the valid use case)
const config = { host: 'localhost', port: 8080 };
// Works, but includes inherited enumerable properties
for (const key in config) {
console.log(key, config[key]);
}
// Better: Object.entries gives you key-value pairs without inheritance issues
for (const [key, value] of Object.entries(config)) {
console.log(key, value);
}
1. for...in includes inherited enumerable properties
const parent = { inherited: true };
const child = Object.create(parent);
child.own = 'value';
for (const key in child) {
console.log(key); // 'own', 'inherited' (both appear!)
}
// Filter to own properties only:
for (const key in child) {
if (Object.hasOwn(child, key)) {
console.log(key); // 'own' only
}
}
2. forEach can't break and doesn't await
// CAN'T BREAK
items.forEach(item => {
if (item.done) return; // This only skips current iteration!
// break; // SyntaxError: break doesn't work here
});
// DOESN'T AWAIT
items.forEach(async item => {
await processItem(item); // These all fire at once!
});
console.log('Done'); // Runs immediately, doesn't wait
Fix: Use for...of for break/continue and sequential async:
for (const item of items) {
if (item.done) break; // Works!
await processItem(item); // Sequential!
}
3. reduce without initial value throws on empty arrays
// THROWS on empty array
[].reduce((sum, n) => sum + n); // TypeError!
// SAFE with initial value
[].reduce((sum, n) => sum + n, 0); // Returns 0
The classic for loop gives you index access, break/continue, and full control:
// Index-based iteration
for (let i = 0; i < items.length; i++) {
console.log(i, items[i]);
}
// Iterate backwards
for (let i = items.length - 1; i >= 0; i--) {
console.log(items[i]);
}
// Skip every other item
for (let i = 0; i < items.length; i += 2) {
console.log(items[i]);
}
// Early exit with break
for (let i = 0; i < items.length; i++) {
if (found(items[i])) break;
}
When to use classic for vs for...of
| Use classic for when... | Use for...of when... |
|---|---|
| You need the index for logic | You only need values |
| Iterating backwards | Forward iteration |
| Custom step (i += 2) | Every element |
| Modifying index mid-loop | Simple iteration |
Labeled statements: breaking out of nested loops
When you need to break out of an outer loop from inside an inner loop, use a label:
outer: for (let i = 0; i < matrix.length; i++) {
for (let j = 0; j < matrix[i].length; j++) {
if (matrix[i][j] === target) {
console.log(`Found at [${i}][${j}]`);
break outer; // Breaks out of BOTH loops
}
}
}
Labels are rare in modern code (extracting to a function with return is often cleaner), but they're the only way to break outer loops without flags.
Use while when iteration count is unknown:
// Retry with backoff
let attempts = 0;
while (attempts < maxRetries) {
const result = await tryOperation();
if (result.success) break;
attempts++;
await sleep(2 ** attempts * 100);
}
// Process queue until empty
while (queue.length > 0) {
const item = queue.shift();
process(item);
}
// Read until sentinel
let line;
while ((line = readline()) !== null) {
process(line);
}
do...while: always runs at least once
let input;
do {
input = prompt('Enter a number > 0:');
} while (isNaN(input) || input <= 0);
Transformation: map
Returns a new array with each element transformed:
const doubled = nums.map(n => n * 2);
const names = users.map(u => u.name);
Filtering: filter
Returns elements that pass the test:
const adults = users.filter(u => u.age >= 18);
const nonEmpty = strings.filter(s => s.length > 0);
Pipeline pattern: filter + map
const activeUserNames = users
.filter(u => u.active)
.map(u => u.name);
Short-circuit methods: find, some, every
These stop as soon as the answer is known:
// find: first match or undefined
const admin = users.find(u => u.role === 'admin');
// findIndex: index of first match or -1
const idx = users.findIndex(u => u.role === 'admin');
// some: does ANY element pass?
const hasAdmin = users.some(u => u.role === 'admin');
// every: do ALL elements pass?
const allActive = users.every(u => u.active);
Aggregation: reduce
// Sum (always include initial value!)
const total = nums.reduce((sum, n) => sum + n, 0);
// Build object from array
const byId = users.reduce((acc, user) => {
acc[user.id] = user;
return acc;
}, {});
Object.entries: key-value pairs
const config = { host: 'localhost', port: 8080 };
for (const [key, value] of Object.entries(config)) {
console.log(`${key}: ${value}`);
}
Object.keys: just keys
for (const key of Object.keys(config)) {
console.log(key);
}
Object.values: just values
for (const value of Object.values(config)) {
console.log(value);
}
All three methods return arrays, so you can use array methods. For a printable reference of all array methods, see the JavaScript array methods cheat sheet.
const hasPort = Object.keys(config).includes('port');
const values = Object.values(config).filter(v => v != null);
| Pattern | Code |
|---|---|
| Iterate values | for (const x of arr) |
| With index | for (const [i, x] of arr.entries()) |
| Object key-value | for (const [k, v] of Object.entries(obj)) |
| Transform | arr.map(x => f(x)) |
| Filter | arr.filter(x => test(x)) |
| First match | arr.find(x => test(x)) |
| Any match? | arr.some(x => test(x)) |
| All match? | arr.every(x => test(x)) |
| Aggregate | arr.reduce((acc, x) => ..., init) |
| Side effects | arr.forEach(x => sideEffect(x)) |
| Async iteration | for await (const x of asyncIterable) |
When to Use JavaScript Loops
- Use Array.map() to transform every element into a new array (pure transform).
- Use Array.filter() to keep only elements that pass a test.
- Use Array.find()/findIndex() when you need the first match and want to stop early.
- Use Array.some()/every() for boolean checks that stop early when possible.
- Use for...of (or classic for) when you need break/continue, early exit, or index access.
- Use Object.entries()/Object.keys()/Object.values() to iterate plain objects predictably.
- Use reduce for true aggregations, but always include an initial value.
Check Your Understanding: JavaScript Loops
Return the names of active users from an array of objects.
Use a readable, non-mutating pipeline: ```js const names = users .filter(u => u.active) .map(u => u.name); ``` Mention: filter/map are clear for transformations; avoid mutation for maintainability.
What You'll Practice: JavaScript Loops
Common JavaScript Loops Pitfalls
- for...in iterates keys as strings and includes inherited enumerable properties
- forEach cannot be broken out of (use for/for...of or find/some/every instead)
- forEach doesn't await async callbacks (use for...of or map + Promise.all)
- reduce without an initial value throws on empty arrays
- Using map for side effects (creates unused array, confuses intent)
- Mutating an array while iterating (skipped elements, weird bugs)
- for...in on arrays gives string keys, not numeric indices
JavaScript Loops FAQ
What's the difference between for...of and for...in?
for...of iterates values of iterables (arrays, strings, sets, maps). for...in iterates enumerable string keys, including inherited enumerable properties. Use for...of for arrays; use for...in carefully for objects only.
Why not use for...in on arrays?
It iterates keys as strings ('0', '1', '2'), not values. It also includes inherited enumerable properties and doesn't guarantee order. Use for...of or a classic for loop for arrays.
Can I break out of forEach?
No. forEach always iterates every element. Use for/for...of for break/continue, or use find/some/every which stop early when the condition is met.
How do I loop over an object's key/value pairs?
Use `Object.entries(obj)` with `for...of`: `for (const [key, value] of Object.entries(obj)) { ... }`. This gives you predictable iteration without inherited properties. For `Map` and `Set` iteration, see the [collections page](/javascript/collections).
Why does reduce sometimes throw on empty arrays?
If you omit the initial value and the array is empty, reduce can't pick a starting accumulator and throws TypeError. Always provide an initial value: `arr.reduce((acc, x) => ..., initialValue)`.
Should I use map for side effects?
No. map is for transformation and returns a new array. Using it for side effects (logging, API calls) creates a discarded array and confuses intent. Use forEach for side effects, or a for loop when you need control flow.
When should I use a while loop?
Use while when the stopping condition isn't tied to iterating a collection: polling, retries, reading until EOF, game loops, or when you don't know iteration count upfront.
Why doesn't await work inside forEach?
forEach ignores the promises returned by async callbacks. They all fire simultaneously and forEach returns undefined immediately. Use `for...of` for sequential async iteration, `for await...of` for async iterables, or `map` + `Promise.all` for parallel execution. See the [async/await page](/javascript/async) for detailed patterns.
What do find, some, and every return?
find returns the first matching element (or undefined). some returns true if any element passes the test. every returns true only if all elements pass. All three stop early when the answer is determined.
How do I get the index in a for...of loop?
Use the `.entries()` method: `for (const [index, item] of items.entries()) { ... }`. This is the modern, idiomatic pattern that combines for...of's readability with index access. No need for forEach or manual counters. Use a classic for loop only when you need to manipulate the index (skip items, iterate backwards).
JavaScript Loops Syntax Quick Reference
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (done) break; // Can break early
}for (const item of items) {
if (item.done) break;
console.log(item);
}for (const [i, item] of items.entries()) {
console.log(i, item);
}for (const [key, value] of Object.entries(obj)) {
console.log(key, value);
}const firstAdult = users.find(u => u.age >= 18);
const index = users.findIndex(u => u.age >= 18);const hasAdmin = users.some(u => u.role === 'admin');
const allActive = users.every(u => u.active);const names = users
.filter(u => u.active)
.map(u => u.name);const total = nums.reduce((sum, n) => sum + n, 0);items.forEach(item => {
console.log(item); // Side effect
});while (queue.length > 0) {
const item = queue.shift();
process(item);
}for await (const chunk of readableStream) {
await process(chunk);
}JavaScript Loops Sample Exercises
Write a do-while loop that runs while `n > 0`.
do {
} while (n > 0);
How many times does the body execute?
1Fill in the keyword that starts a loop where the body runs before the condition is checked.
do+ 17 more exercises