for Loops

โ–ถ Try It Yourself

Loops are how programs do repetitive work without repetitive code. JavaScript has several loop types โ€” each suited to different situations. In this lesson you will master the classic for loop, the modern for...of loop for iterating arrays and other iterables, and the for...in loop for enumerating object keys. You will also learn break and continue for controlling loop execution.

JavaScript Loop Types

Loop Best For Example
for Known iteration count; index access needed for (let i = 0; i < 10; i++)
for...of Arrays, strings, Sets, Maps โ€” value iteration for (const item of items)
for...in Object key enumeration for (const key in obj)
while Unknown iteration count โ€” condition-based while (queue.length > 0)
do...while Must run at least once before checking do { prompt() } while (!valid)
Array methods Functional iteration โ€” no explicit loop items.forEach(), items.map()

Classic for Loop Anatomy

Part Runs When Example
Initialiser Once before the loop starts let i = 0
Condition Before every iteration โ€” loop continues while true i < arr.length
Update After every iteration i++
Body Each iteration while condition is true { console.log(arr[i]); }

break and continue

Keyword Effect Use Case
break Exits the loop entirely Found the item you were searching for
continue Skips the rest of this iteration; moves to next Skip invalid or irrelevant items
Note: for...of iterates over the values of an iterable. for...in iterates over the keys (property names) of an object. Never use for...in on an array โ€” it iterates string keys like '0', '1', and also picks up any enumerable properties added to Array.prototype by third-party libraries. Always use for...of or array methods for arrays.
Tip: When you need both the index and value while iterating an array, use for...of with entries(): for (const [index, value] of items.entries()). This is cleaner than a classic for loop and avoids the need to manually reference items[i]. It also works with any iterable, not just arrays.
Warning: Infinite loops crash your browser tab or Node.js process. The most common cause is a loop condition that never becomes false โ€” either the update expression is missing, the condition variable is never modified inside the loop body, or you accidentally used = instead of === in the condition. Always double-check that your loop has a clear termination path.

Basic Example

// โ”€โ”€ Classic for loop โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const fruits = ['apple', 'banana', 'cherry', 'date', 'elderberry'];

for (let i = 0; i < fruits.length; i++) {
    console.log(`${i}: ${fruits[i]}`);
}
// 0: apple | 1: banana | 2: cherry | 3: date | 4: elderberry

// โ”€โ”€ Reverse iteration โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
for (let i = fruits.length - 1; i >= 0; i--) {
    console.log(fruits[i]);
}
// elderberry, date, cherry, banana, apple

// โ”€โ”€ for...of โ€” cleanest for array values โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
for (const fruit of fruits) {
    console.log(fruit.toUpperCase());
}

// โ”€โ”€ for...of with entries() โ€” index + value โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
for (const [index, fruit] of fruits.entries()) {
    const label = index === 0 ? '(first)' : index === fruits.length - 1 ? '(last)' : '';
    console.log(`${index}: ${fruit} ${label}`);
}

// โ”€โ”€ for...in โ€” object keys โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const user = { name: 'Alice', age: 30, role: 'admin', isActive: true };

for (const key in user) {
    console.log(`${key}: ${user[key]}`);
}
// name: Alice | age: 30 | role: admin | isActive: true

// โ”€โ”€ break โ€” exit when found โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const numbers = [4, 8, 15, 16, 23, 42];
let target    = 15;
let foundAt   = -1;

for (let i = 0; i < numbers.length; i++) {
    if (numbers[i] === target) {
        foundAt = i;
        break;   // stop searching once found
    }
}
console.log(`Found ${target} at index ${foundAt}`);  // Found 15 at index 2

// โ”€โ”€ continue โ€” skip invalid items โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const rawScores = [88, null, 72, undefined, 95, NaN, 60];
const validScores = [];

for (const score of rawScores) {
    if (score == null || !Number.isFinite(score)) {
        continue;   // skip nulls, undefined, NaN
    }
    validScores.push(score);
}
console.log(validScores);  // [88, 72, 95, 60]

// โ”€โ”€ Nested loops โ€” multiplication table โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const SIZE = 4;
for (let row = 1; row <= SIZE; row++) {
    let line = '';
    for (let col = 1; col <= SIZE; col++) {
        line += String(row * col).padStart(4);
    }
    console.log(line);
}
//    1   2   3   4
//    2   4   6   8
//    3   6   9  12
//    4   8  12  16

// โ”€โ”€ for...of on a string โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const word = 'hello';
const chars = [];
for (const char of word) {
    chars.push(char.toUpperCase());
}
console.log(chars.join('-'));  // H-E-L-L-O

How It Works

Step 1 โ€” Classic for Loop Execution Order

The initialiser (let i = 0) runs once. Then: check condition โ†’ run body โ†’ run update โ†’ check condition โ†’ run body โ†’ run update… until the condition is false. The condition is checked before each iteration, so if it is false from the start, the body never runs.

Step 2 โ€” for…of Uses the Iterable Protocol

for...of works on anything that implements the iterable protocol โ€” arrays, strings, Sets, Maps, NodeLists, generator functions. It calls the object’s [Symbol.iterator]() method internally and steps through values one by one. You get the value directly โ€” no index variable needed.

Step 3 โ€” for…in Enumerates Prototype Chain

for...in on an object yields all enumerable property keys, including inherited ones from the prototype chain. For plain objects created with { } this is usually fine. For arrays, avoid it โ€” use for...of or array methods instead.

Step 4 โ€” break Exits to the Outer Scope

break inside a nested loop only exits the innermost loop. To break out of an outer loop from inside a nested loop, use a labelled break: outer: for (...) { inner: for (...) { break outer; } }. This is rarely needed but useful for matrix searches.

Step 5 โ€” continue Jumps to the Next Iteration

continue skips the rest of the current iteration’s body and moves directly to the update expression (in a classic for loop) or the next value (in for…of). Using continue to skip invalid items at the top of a loop body is a clean guard-clause equivalent for loops.

Real-World Example: Data Pipeline

// data-pipeline.js

const orders = [
    { id: 'A1', status: 'completed', total: 49.99,  items: 3 },
    { id: 'A2', status: 'pending',   total: 12.50,  items: 1 },
    { id: 'A3', status: 'completed', total: 199.00, items: 7 },
    { id: 'A4', status: 'cancelled', total: 30.00,  items: 2 },
    { id: 'A5', status: 'completed', total: 89.95,  items: 4 },
    { id: 'A6', status: 'pending',   total: 0,      items: 0 },
];

// Aggregate completed orders
let completedRevenue = 0;
let completedCount   = 0;
let largestOrder     = null;

for (const order of orders) {
    // Skip non-completed and invalid orders
    if (order.status !== 'completed') continue;
    if (order.total <= 0)            continue;

    completedRevenue += order.total;
    completedCount++;

    // Track largest order
    if (!largestOrder || order.total > largestOrder.total) {
        largestOrder = order;
    }
}

console.log(`Completed orders: ${completedCount}`);
console.log(`Total revenue:    $${completedRevenue.toFixed(2)}`);
console.log(`Largest order:    ${largestOrder?.id} ($${largestOrder?.total})`);

// Build a report object from for...in
const statusCounts = {};
for (const order of orders) {
    const s = order.status;
    statusCounts[s] = (statusCounts[s] ?? 0) + 1;
}

console.log('
Status breakdown:');
for (const [status, count] of Object.entries(statusCounts)) {
    const bar = '#'.repeat(count * 3).padEnd(15);
    console.log(`  ${status.padEnd(10)} ${bar} ${count}`);
}

Common Mistakes

Mistake 1 โ€” Using for…in on an array

โŒ Wrong โ€” for…in gives string keys and may include prototype properties:

const arr = ['a', 'b', 'c'];
for (const i in arr) {
    console.log(i);   // '0', '1', '2' โ€” strings, not numbers
}

โœ… Correct โ€” use for…of for array values:

for (const item of arr) {
    console.log(item);  // 'a', 'b', 'c' โ€” the actual values
}

Mistake 2 โ€” Off-by-one error in classic for loop

โŒ Wrong โ€” <= length goes one past the last index:

for (let i = 0; i <= arr.length; i++) {
    console.log(arr[i]);  // last iteration: arr[3] is undefined
}

โœ… Correct โ€” use strictly less than:

for (let i = 0; i < arr.length; i++) {
    console.log(arr[i]);  // stops at index 2 for a 3-element array
}

Mistake 3 โ€” Modifying an array while iterating it

โŒ Wrong โ€” pushing to the array inside for…of causes unexpected extra iterations:

const items = [1, 2, 3];
for (const item of items) {
    items.push(item * 2);  // infinite loop โ€” array keeps growing
}

โœ… Correct โ€” build a new array instead:

const doubled = [];
for (const item of items) {
    doubled.push(item * 2);  // safe โ€” not modifying items while iterating
}

▶ Try It Yourself

Quick Reference

Loop Syntax Use For
for for (let i=0; i<n; i++) Index needed; known count
for...of for (const v of iterable) Arrays, strings, Sets, Maps
for...of entries() for (const [i,v] of arr.entries()) Index + value both needed
for...in for (const k in obj) Object keys only โ€” never arrays
break break; Exit loop early
continue continue; Skip to next iteration

🧠 Test Yourself

You need both the index and value when iterating an array. What is the cleanest modern approach?





โ–ถ Try It Yourself