for Loop
The classic for loop gives full control over initialization, condition, and
increment. Use it when you need the index or precise control over iteration.
// Basic for loop: init; condition; update
for (let i = 0; i < 5; i++) {
console.log(i); // 0, 1, 2, 3, 4
}
// Iterating an array with index
const fruits = ["apple", "banana", "cherry"];
for (let i = 0; i < fruits.length; i++) {
console.log(`${i}: ${fruits[i]}`);
}
// 0: apple, 1: banana, 2: cherry
// Reverse iteration
for (let i = fruits.length - 1; i >= 0; i--) {
console.log(fruits[i]); // cherry, banana, apple
}
// Step by 2
for (let i = 0; i <= 10; i += 2) {
console.log(i); // 0, 2, 4, 6, 8, 10
}
// Nested loops
for (let row = 1; row <= 3; row++) {
for (let col = 1; col <= 3; col++) {
process.stdout.write(`(${row},${col}) `);
}
console.log();
}
for...of
for...of iterates over the values of any iterable: arrays,
strings, Maps, Sets, and generators. It is the modern default for iteration.
// Arrays - iterates values
const fruits = ["apple", "banana", "cherry"];
for (const fruit of fruits) {
console.log(fruit); // apple, banana, cherry
}
// With index using entries()
for (const [index, fruit] of fruits.entries()) {
console.log(`${index}: ${fruit}`);
}
// 0: apple, 1: banana, 2: cherry
// Strings - iterates characters
for (const char of "hello") {
console.log(char); // h, e, l, l, o
}
// Set - unique values
const set = new Set([1, 2, 3, 2, 1]);
for (const val of set) {
console.log(val); // 1, 2, 3
}
// Map - key-value pairs
const map = new Map([["a", 1], ["b", 2]]);
for (const [key, value] of map) {
console.log(`${key} = ${value}`); // a = 1, b = 2
}
// Async iteration with await (sequential)
async function processItems(items) {
for (const item of items) {
await processItem(item); // waits for each to complete
}
}
for...in
for...in iterates over the enumerable property keys of an
object. Avoid using it on arrays - use for...of or Object.keys() instead.
// for...in on objects
const user = { name: "Alice", age: 28, city: "London" };
for (const key in user) {
console.log(`${key}: ${user[key]}`);
}
// name: Alice, age: 28, city: London
// Prefer Object.entries for cleaner code
for (const [key, value] of Object.entries(user)) {
console.log(`${key}: ${value}`);
}
// WARNING: for...in on arrays - avoid!
const arr = ["a", "b", "c"];
for (const key in arr) {
console.log(key); // "0", "1", "2" - string indices!
// Also iterates any inherited or added properties
}
// for...in includes inherited properties
function Person(name) { this.name = name; }
Person.prototype.greet = function() {};
const p = new Person("Alice");
for (const key in p) {
console.log(key); // "name", "greet" (inherited!)
}
// Check own properties only:
for (const key in p) {
if (Object.hasOwn(p, key)) console.log(key); // "name" only
}
for...in iterates string keys and includes inherited enumerable properties. On arrays, this can give unexpected results. For arrays, always use for...of, forEach, or map/filter/reduce. Reserve for...in only for plain objects when you need to enumerate all keys.
while and do...while
while repeats a block while a condition is true. Use it when the number of
iterations is not known in advance. do...while always runs the body at least once.
// while - check BEFORE running body
let count = 0;
while (count < 5) {
console.log(count); // 0, 1, 2, 3, 4
count++;
}
// If count starts at 5, body never runs
// Practical: read until a condition is met
let input = "";
while (!isValid(input)) {
input = getUserInput();
}
// do...while - check AFTER running body (runs at least once)
let attempts = 0;
do {
console.log(`Attempt ${++attempts}`);
} while (attempts < 3);
// Attempt 1, Attempt 2, Attempt 3
// do...while guarantees one run even if condition starts false
let x = 10;
do {
console.log("Runs once even though x >= 5"); // runs
} while (x < 5);
forEach vs map
Both iterate arrays. The key difference: map() returns a new array;
forEach() returns undefined and is for side effects only.
const nums = [1, 2, 3, 4, 5];
// forEach - side effects (logging, DOM updates)
nums.forEach((n, index) => {
console.log(`${index}: ${n}`);
});
// map - transformation, returns new array
const doubled = nums.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(nums); // [1, 2, 3, 4, 5] - original unchanged
// forEach vs for...of for async
// WRONG - forEach with async does not await properly
nums.forEach(async n => {
await fetch(`/api/${n}`); // all fire concurrently, no order
});
// CORRECT - for...of awaits sequentially
for (const n of nums) {
await fetch(`/api/${n}`); // waits for each one
}
// CORRECT - parallel with Promise.all
await Promise.all(nums.map(async n => fetch(`/api/${n}`)));
break and continue
// break - exit the loop entirely
const nums = [1, 3, 7, 10, 14, 20];
for (const n of nums) {
if (n >= 10) {
console.log(`First number >= 10: ${n}`); // 10
break;
}
}
// continue - skip to the next iteration
for (let i = 0; i < 10; i++) {
if (i % 2 === 0) continue; // skip evens
console.log(i); // 1, 3, 5, 7, 9
}
// Labeled break - exit outer loop from inner loop
outer: for (let row = 0; row < 3; row++) {
for (let col = 0; col < 3; col++) {
if (row === 1 && col === 1) break outer; // exits both loops
console.log(`${row},${col}`);
}
}
// 0,0 0,1 0,2 1,0 (stops here)
// break works in switch too (prevents fall-through)
switch (key) {
case "a": doA(); break;
case "b": doB(); break;
}
Loop Comparison
| Loop | Iterates | break/continue | Returns value | Best for |
|---|---|---|---|---|
for | By index | Yes | No | Index control, reverse, step |
for...of | Iterable values | Yes | No | Arrays, strings, Maps, Sets |
for...in | Object keys | Yes | No | Object property enumeration |
while | While condition | Yes | No | Unknown iteration count |
do...while | While condition | Yes | No | Must run at least once |
forEach | Array values | No | undefined | Side effects on arrays |
map | Array values | No | New array | Transform every element |
filter | Array values | No | New array | Keep matching elements |