JavaScript Loops

JavaScript has six ways to loop. Learn each one, when to pick it, and how break, continue, and async patterns interact with iteration.

Beginner 10 min read 10 examples

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.

JavaScript
// 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.

JavaScript
// 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.

JavaScript
// 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
}
Do Not Use for...in on Arrays

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.

JavaScript
// 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.

JavaScript
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

JavaScript
// 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

LoopIteratesbreak/continueReturns valueBest for
forBy indexYesNoIndex control, reverse, step
for...ofIterable valuesYesNoArrays, strings, Maps, Sets
for...inObject keysYesNoObject property enumeration
whileWhile conditionYesNoUnknown iteration count
do...whileWhile conditionYesNoMust run at least once
forEachArray valuesNoundefinedSide effects on arrays
mapArray valuesNoNew arrayTransform every element
filterArray valuesNoNew arrayKeep matching elements

Frequently Asked Questions

Use for...of when you need break, continue, or return (from a containing function) inside the loop body. Use forEach for simple side-effect iteration where you do not need to exit early. Neither can replace map/filter/reduce when you need to produce a transformed array - use those array methods instead.

for...of iterates over the values of an iterable (arrays, strings, Maps, Sets). for...in iterates over the keys (property names) of an object. Avoid for...in on arrays - it iterates string indices and inherited properties. Use for...in only for object property enumeration, and prefer Object.keys/entries instead.

Yes, but the behavior differs by loop type. for...of with await runs iterations sequentially (one after another). forEach with async/await does NOT work correctly - it fires all callbacks concurrently but does not wait for them. For parallel execution, use Promise.all(array.map(async item => ...)). For sequential, use for...of with await.

An infinite loop occurs when the loop condition never becomes false. Common causes: forgetting to increment a counter, accidentally reassigning instead of comparing (= instead of ===), or mutating the array being iterated. Fix: ensure the loop variable changes each iteration and the condition will eventually fail. Add a safety counter if you cannot guarantee termination.