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 |
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.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.= 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
}
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 |