JavaScript — Loops & Iteration
Loops are fundamental control structures that execute a block of code repeatedly until a specified condition evaluates to false. JavaScript provides multiple loop constructs, each suited for different scenarios: for, while, do...while, for...in, and for...of.
Modern JavaScript also offers array iteration methods (forEach, map, filter, reduce) that often replace traditional loops with more declarative code. Understanding when to use each loop type and iteration method is essential for writing efficient, readable JavaScript.
This page covers every loop construct in detail, their performance characteristics, common patterns, and best practices. We will also explore break, continue, labeled statements, and the modern iteration protocols.
| 1 | // A loop repeats code until a condition is false |
| 2 | // Basic for loop — classic counter-based iteration |
| 3 | for (let i = 0; i < 5; i++) { |
| 4 | console.log("Iteration:", i); |
| 5 | } |
| 6 | // Output: 0, 1, 2, 3, 4 |
| 7 | |
| 8 | // While loop — runs while condition is true |
| 9 | let count = 0; |
| 10 | while (count < 5) { |
| 11 | console.log("Count:", count); |
| 12 | count++; |
| 13 | } |
| 14 | // Output: 0, 1, 2, 3, 4 |
| 15 | |
| 16 | // do...while — always runs at least once |
| 17 | let x = 0; |
| 18 | do { |
| 19 | console.log("x:", x); |
| 20 | x++; |
| 21 | } while (x < 3); |
| 22 | // Output: 0, 1, 2 |
| 23 | |
| 24 | // Modern JavaScript favors declarative iteration |
| 25 | const numbers = [10, 20, 30, 40, 50]; |
| 26 | |
| 27 | // for...of — iterate over iterable values |
| 28 | for (const num of numbers) { |
| 29 | console.log(num); // 10, 20, 30, 40, 50 |
| 30 | } |
| 31 | |
| 32 | // Array methods — often preferred |
| 33 | numbers.forEach((num, i) => { |
| 34 | console.log(`Index ${i}: ${num}`); |
| 35 | }); |
| 36 | const doubled = numbers.map(n => n * 2); |
| 37 | const evens = numbers.filter(n => n % 2 === 0); |
| 38 | const sum = numbers.reduce((acc, n) => acc + n, 0); |
The for loop is the most traditional and flexible loop in JavaScript. It consists of three optional expressions: initialization, condition, and final expression. The loop body executes while the condition is truthy.
| 1 | // Standard for loop — initialize, condition, increment |
| 2 | for (let i = 0; i < 10; i++) { |
| 3 | console.log(i); |
| 4 | } |
| 5 | |
| 6 | // Multiple variables in initialization |
| 7 | for (let i = 0, j = 10; i < j; i++, j--) { |
| 8 | console.log(i, j); |
| 9 | } |
| 10 | |
| 11 | // Empty initialization (variables declared outside) |
| 12 | let k = 0; |
| 13 | for (; k < 5; k++) { |
| 14 | console.log(k); |
| 15 | } |
| 16 | |
| 17 | // Infinite loop — condition is omitted (truthy by default) |
| 18 | // for (;;) { console.log("forever"); } |
| 19 | |
| 20 | // Reverse iteration |
| 21 | for (let i = 10; i >= 0; i--) { |
| 22 | console.log(i); |
| 23 | } |
| 24 | |
| 25 | // Stepping by 2, 5, etc. |
| 26 | for (let i = 0; i < 20; i += 3) { |
| 27 | console.log("Step 3:", i); |
| 28 | } |
| 29 | |
| 30 | // Iterating an array with index |
| 31 | const fruits = ["apple", "banana", "cherry"]; |
| 32 | for (let i = 0; i < fruits.length; i++) { |
| 33 | console.log(`${i}: ${fruits[i]}`); |
| 34 | } |
| 35 | |
| 36 | // Caching array length for performance (rarely matters now) |
| 37 | for (let i = 0, len = fruits.length; i < len; i++) { |
| 38 | console.log(fruits[i]); |
| 39 | } |
| 40 | |
| 41 | // Nested for loops — multi-dimensional arrays |
| 42 | const matrix = [ |
| 43 | [1, 2, 3], |
| 44 | [4, 5, 6], |
| 45 | [7, 8, 9], |
| 46 | ]; |
| 47 | for (let row = 0; row < matrix.length; row++) { |
| 48 | for (let col = 0; col < matrix[row].length; col++) { |
| 49 | console.log(`matrix[${row}][${col}] = ${matrix[row][col]}`); |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | // for loop without body — for side effects only |
| 54 | let arr = []; |
| 55 | for (let i = 0; i < 5; arr.push(i++)); |
| 56 | console.log(arr); // [0, 1, 2, 3, 4] |
| 57 | |
| 58 | // Loop with continue to skip iterations |
| 59 | for (let i = 0; i < 10; i++) { |
| 60 | if (i % 2 === 0) continue; // skip evens |
| 61 | console.log("Odd:", i); // 1, 3, 5, 7, 9 |
| 62 | } |
| 63 | |
| 64 | // Loop with break to exit early |
| 65 | for (let i = 0; i < 100; i++) { |
| 66 | if (i === 5) break; |
| 67 | console.log(i); // 0, 1, 2, 3, 4 |
| 68 | } |
info
The while loop evaluates a condition before each iteration; if the condition is falsy initially, the body never executes. The do...while loop evaluates the condition after the body, guaranteeing at least one execution. Use while loops when the number of iterations is not known in advance.
| 1 | // while loop — condition checked before each iteration |
| 2 | let i = 0; |
| 3 | while (i < 5) { |
| 4 | console.log("while:", i); |
| 5 | i++; |
| 6 | } |
| 7 | |
| 8 | // while loop with complex condition |
| 9 | let attempts = 0; |
| 10 | let maxAttempts = 3; |
| 11 | let connected = false; |
| 12 | |
| 13 | while (attempts < maxAttempts && !connected) { |
| 14 | console.log(`Attempt ${attempts + 1}`); |
| 15 | connected = tryConnect(); // returns true/false |
| 16 | attempts++; |
| 17 | } |
| 18 | |
| 19 | // do...while — always runs at least once |
| 20 | let j = 0; |
| 21 | do { |
| 22 | console.log("do-while:", j); |
| 23 | j++; |
| 24 | } while (j < 5); |
| 25 | |
| 26 | // Guaranteed execution with do...while |
| 27 | let input; |
| 28 | do { |
| 29 | input = prompt("Enter a number greater than 0:"); |
| 30 | } while (input <= 0); |
| 31 | |
| 32 | // while loop for reading from a stream |
| 33 | async function readStream(reader) { |
| 34 | let result = ""; |
| 35 | while (true) { |
| 36 | const { done, value } = await reader.read(); |
| 37 | if (done) break; |
| 38 | result += value; |
| 39 | } |
| 40 | return result; |
| 41 | } |
| 42 | |
| 43 | // while with sentinel value |
| 44 | function findFirstNegative(numbers) { |
| 45 | let i = 0; |
| 46 | while (i < numbers.length && numbers[i] >= 0) { |
| 47 | i++; |
| 48 | } |
| 49 | return i < numbers.length ? i : -1; |
| 50 | } |
| 51 | |
| 52 | // Infinite loop with break inside |
| 53 | let counter = 0; |
| 54 | while (true) { |
| 55 | counter++; |
| 56 | if (counter >= 100) break; |
| 57 | if (counter % 10 === 0) { |
| 58 | console.log("Multiple of 10:", counter); |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | // do...while for retry logic |
| 63 | let success = false; |
| 64 | let retries = 0; |
| 65 | do { |
| 66 | try { |
| 67 | success = performOperation(); |
| 68 | } catch (e) { |
| 69 | console.error("Attempt ${retries + 1} failed"); |
| 70 | success = false; |
| 71 | } |
| 72 | retries++; |
| 73 | } while (!success && retries < 3); |
best practice
The for...in loop iterates over all enumerable string properties of an object, including inherited ones from the prototype chain. It is designed for objects, not arrays. Using it on arrays can produce unexpected results because it iterates over enumerable properties (including array indices and any custom properties added to the array).
| 1 | // for...in iterates over object keys |
| 2 | const user = { |
| 3 | name: "Alice", |
| 4 | age: 30, |
| 5 | role: "admin", |
| 6 | }; |
| 7 | |
| 8 | for (const key in user) { |
| 9 | console.log(`${key}: ${user[key]}`); |
| 10 | } |
| 11 | // Output: name: Alice |
| 12 | // age: 30 |
| 13 | // role: admin |
| 14 | |
| 15 | // Checking own vs inherited properties |
| 16 | class Animal { |
| 17 | constructor(name) { |
| 18 | this.name = name; |
| 19 | } |
| 20 | speak() { |
| 21 | console.log(`${this.name} makes a sound`); |
| 22 | } |
| 23 | } |
| 24 | |
| 25 | class Dog extends Animal { |
| 26 | constructor(name, breed) { |
| 27 | super(name); |
| 28 | this.breed = breed; |
| 29 | } |
| 30 | } |
| 31 | |
| 32 | const dog = new Dog("Rex", "German Shepherd"); |
| 33 | |
| 34 | for (const key in dog) { |
| 35 | console.log(key); // "name", "breed" |
| 36 | // Note: "speak" is on the prototype, not enumerable |
| 37 | } |
| 38 | |
| 39 | // Use hasOwnProperty to filter inherited properties |
| 40 | for (const key in dog) { |
| 41 | if (Object.hasOwn(dog, key)) { |
| 42 | console.log("Own property:", key, dog[key]); |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | // for...in on arrays — iterate over indices (strings!) |
| 47 | const arr = ["a", "b", "c"]; |
| 48 | arr.customProp = "hello"; |
| 49 | |
| 50 | for (const index in arr) { |
| 51 | console.log(index, typeof index); // "0" (string), "1", "2", "customProp" |
| 52 | } |
| 53 | |
| 54 | // Better: use for...of for arrays |
| 55 | for (const value of arr) { |
| 56 | console.log(value); // "a", "b", "c" (only actual values) |
| 57 | } |
| 58 | |
| 59 | // Enumerating object keys with Object.keys() |
| 60 | Object.keys(user).forEach(key => { |
| 61 | console.log(key, user[key]); |
| 62 | }); |
| 63 | |
| 64 | // Enumerating key-value pairs |
| 65 | Object.entries(user).forEach(([key, value]) => { |
| 66 | console.log(`${key}: ${value}`); |
| 67 | }); |
| 68 | |
| 69 | // for...in with null/undefined prototype |
| 70 | const dict = Object.create(null); |
| 71 | dict.foo = "bar"; |
| 72 | for (const key in dict) { |
| 73 | console.log(key); // "foo" — works, no prototype pollution |
| 74 | } |
warning
Introduced in ES2015 (ES6), the for...of loop iterates over iterable objects — Arrays, Strings, Maps, Sets, TypedArrays, NodeLists, generators, and any object implementing the [Symbol.iterator] protocol. It yields the values directly, not the indices or keys.
| 1 | // for...of iterates over VALUES (not indices) |
| 2 | const colors = ["red", "green", "blue"]; |
| 3 | |
| 4 | for (const color of colors) { |
| 5 | console.log(color); // "red", "green", "blue" |
| 6 | } |
| 7 | |
| 8 | // With index — use .entries() |
| 9 | for (const [index, color] of colors.entries()) { |
| 10 | console.log(`${index}: ${color}`); |
| 11 | } |
| 12 | |
| 13 | // Iterating over a string |
| 14 | for (const char of "hello") { |
| 15 | console.log(char); // "h", "e", "l", "l", "o" |
| 16 | } |
| 17 | |
| 18 | // for...of respects Unicode code points |
| 19 | for (const char of "🌟🔥🎉") { |
| 20 | console.log(char); // works correctly with emoji |
| 21 | } |
| 22 | |
| 23 | // Iterating over a Map |
| 24 | const userMap = new Map([ |
| 25 | ["name", "Alice"], |
| 26 | ["age", 30], |
| 27 | ["role", "admin"], |
| 28 | ]); |
| 29 | |
| 30 | for (const [key, value] of userMap) { |
| 31 | console.log(`${key}: ${value}`); |
| 32 | } |
| 33 | |
| 34 | // Iterating over a Set |
| 35 | const uniqueNumbers = new Set([1, 2, 3, 2, 1]); |
| 36 | for (const num of uniqueNumbers) { |
| 37 | console.log(num); // 1, 2, 3 (duplicates removed) |
| 38 | } |
| 39 | |
| 40 | // Iterating over a NodeList (DOM) |
| 41 | // const paragraphs = document.querySelectorAll("p"); |
| 42 | // for (const p of paragraphs) { |
| 43 | // p.style.color = "#00FF41"; |
| 44 | // } |
| 45 | |
| 46 | // Iterating over arguments |
| 47 | function sum() { |
| 48 | let total = 0; |
| 49 | for (const num of arguments) { |
| 50 | total += num; |
| 51 | } |
| 52 | return total; |
| 53 | } |
| 54 | console.log(sum(1, 2, 3, 4, 5)); // 15 |
| 55 | |
| 56 | // Destructuring in for...of |
| 57 | const points = [ |
| 58 | { x: 1, y: 2 }, |
| 59 | { x: 3, y: 4 }, |
| 60 | { x: 5, y: 6 }, |
| 61 | ]; |
| 62 | |
| 63 | for (const { x, y } of points) { |
| 64 | console.log(`Point: (${x}, ${y})`); |
| 65 | } |
| 66 | |
| 67 | // for...of with generators and async iterators |
| 68 | function* range(start, end) { |
| 69 | for (let i = start; i <= end; i++) { |
| 70 | yield i; |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | for (const n of range(1, 5)) { |
| 75 | console.log(n); // 1, 2, 3, 4, 5 |
| 76 | } |
| 77 | |
| 78 | // Early exit with break |
| 79 | for (const item of hugeArray) { |
| 80 | if (item.id === targetId) { |
| 81 | console.log("Found:", item); |
| 82 | break; |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | // Skipping with continue |
| 87 | for (const item of items) { |
| 88 | if (item.isInactive) continue; |
| 89 | process(item); |
| 90 | } |
pro tip
JavaScript provides keywords to control loop execution from within the body: break terminates the loop entirely, continue skips the current iteration and proceeds to the next, and labels allow breaking or continuing nested loops from an outer scope.
| 1 | // break — exit the loop immediately |
| 2 | for (let i = 0; i < 10; i++) { |
| 3 | if (i === 5) { |
| 4 | break; // stops at 5 |
| 5 | } |
| 6 | console.log(i); // 0, 1, 2, 3, 4 |
| 7 | } |
| 8 | |
| 9 | // continue — skip this iteration |
| 10 | for (let i = 0; i < 10; i++) { |
| 11 | if (i % 2 === 0) { |
| 12 | continue; // skip even numbers |
| 13 | } |
| 14 | console.log(i); // 1, 3, 5, 7, 9 |
| 15 | } |
| 16 | |
| 17 | // break in while loop |
| 18 | let found = false; |
| 19 | let index = 0; |
| 20 | while (index < items.length && !found) { |
| 21 | if (items[index].id === targetId) { |
| 22 | found = true; |
| 23 | break; |
| 24 | } |
| 25 | index++; |
| 26 | } |
| 27 | |
| 28 | // continue skips to the next iteration of while |
| 29 | let i = 0; |
| 30 | while (i < 10) { |
| 31 | i++; |
| 32 | if (i % 3 !== 0) continue; |
| 33 | console.log("Multiple of 3:", i); |
| 34 | } |
| 35 | |
| 36 | // Labeled break — exit outer loop from inner loop |
| 37 | outerLoop: for (let i = 0; i < 3; i++) { |
| 38 | for (let j = 0; j < 3; j++) { |
| 39 | if (i === 1 && j === 1) { |
| 40 | break outerLoop; // breaks BOTH loops |
| 41 | } |
| 42 | console.log(`i=${i}, j=${j}`); |
| 43 | } |
| 44 | } |
| 45 | // Output: (0,0), (0,1), (0,2), (1,0) — then breaks |
| 46 | |
| 47 | // Labeled continue — skip outer iteration |
| 48 | outerContinue: for (let i = 0; i < 3; i++) { |
| 49 | for (let j = 0; j < 3; j++) { |
| 50 | if (j === 1) { |
| 51 | continue outerContinue; // skip to next i |
| 52 | } |
| 53 | console.log(`i=${i}, j=${j}`); |
| 54 | } |
| 55 | } |
| 56 | // Output: (0,0), (1,0), (2,0) — skips j=1 and j=2 for each i |
| 57 | |
| 58 | // Using return to exit a loop in a function |
| 59 | function findFirstEven(numbers) { |
| 60 | for (const num of numbers) { |
| 61 | if (num % 2 === 0) { |
| 62 | return num; // exits both loop and function |
| 63 | } |
| 64 | } |
| 65 | return null; |
| 66 | } |
| 67 | |
| 68 | // break with forEach — NOT possible (forEach doesn't support break) |
| 69 | // Use for...of or some() instead |
| 70 | const foundItem = items.some(item => { |
| 71 | if (item.id === targetId) { |
| 72 | console.log("Found via some()"); |
| 73 | return true; // breaks the iteration |
| 74 | } |
| 75 | return false; |
| 76 | }); |
| 77 | |
| 78 | // Using every() to simulate break (return false to stop) |
| 79 | items.every(item => { |
| 80 | console.log(item); |
| 81 | return item.id !== targetId; // stops when id matches |
| 82 | }); |
info
Loop performance in JavaScript has evolved significantly as engines (V8, SpiderMonkey, JavaScriptCore) have become highly optimized. In most cases, the choice of loop construct has negligible impact on real-world performance. However, certain patterns can be measurably faster for large datasets or performance-critical paths.
| 1 | // Performance comparison of loop types |
| 2 | // Modern JS engines optimize all loops well |
| 3 | |
| 4 | // For comparison, iterate a large array |
| 5 | const BIG = 1_000_000; |
| 6 | const arr = Array.from({ length: BIG }, (_, i) => i); |
| 7 | |
| 8 | // Classic for loop — often fastest for simple numeric iteration |
| 9 | console.time("for"); |
| 10 | let sum1 = 0; |
| 11 | for (let i = 0; i < arr.length; i++) { |
| 12 | sum1 += arr[i]; |
| 13 | } |
| 14 | console.timeEnd("for"); |
| 15 | |
| 16 | // for...of — slightly slower but more readable |
| 17 | console.time("for-of"); |
| 18 | let sum2 = 0; |
| 19 | for (const val of arr) { |
| 20 | sum2 += val; |
| 21 | } |
| 22 | console.timeEnd("for-of"); |
| 23 | |
| 24 | // forEach — similar to for-of |
| 25 | console.time("forEach"); |
| 26 | let sum3 = 0; |
| 27 | arr.forEach(val => { sum3 += val; }); |
| 28 | console.timeEnd("forEach"); |
| 29 | |
| 30 | // Cached length — negligible benefit in modern engines |
| 31 | console.time("for-cached"); |
| 32 | let sum4 = 0; |
| 33 | for (let i = 0, len = arr.length; i < len; i++) { |
| 34 | sum4 += arr[i]; |
| 35 | } |
| 36 | console.timeEnd("for-cached"); |
| 37 | |
| 38 | // Reverse while — sometimes fastest |
| 39 | console.time("while-reverse"); |
| 40 | let sum5 = 0; |
| 41 | let i = arr.length; |
| 42 | while (i--) { |
| 43 | sum5 += arr[i]; |
| 44 | } |
| 45 | console.timeEnd("while-reverse"); |
| 46 | |
| 47 | // Performance tips that still matter: |
| 48 | // 1. Avoid DOM access inside loops |
| 49 | // BAD: |
| 50 | for (let i = 0; i < items.length; i++) { |
| 51 | document.getElementById("result").innerHTML += items[i]; |
| 52 | } |
| 53 | // GOOD: batch DOM updates |
| 54 | let html = ""; |
| 55 | for (const item of items) { |
| 56 | html += item; |
| 57 | } |
| 58 | document.getElementById("result").innerHTML = html; |
| 59 | |
| 60 | // 2. Avoid creating functions inside loops |
| 61 | // BAD (creates a new function each iteration): |
| 62 | for (const item of items) { |
| 63 | process(item, function(result) { |
| 64 | console.log(result); |
| 65 | }); |
| 66 | } |
| 67 | // GOOD: define function once |
| 68 | function handleResult(result) { |
| 69 | console.log(result); |
| 70 | } |
| 71 | for (const item of items) { |
| 72 | process(item, handleResult); |
| 73 | } |
| 74 | |
| 75 | // 3. Use appropriate data structures |
| 76 | // Searching with Set is O(1) vs Array O(n) |
| 77 | const idSet = new Set(largeArray.map(x => x.id)); |
| 78 | for (const item of anotherArray) { |
| 79 | // BAD: largeArray.some(x => x.id === item.id) — O(n) |
| 80 | // GOOD: idSet.has(item.id) — O(1) |
| 81 | if (idSet.has(item.id)) { |
| 82 | // process |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | // 4. Prefer for...of with break for early termination |
| 87 | function findFirst(items, predicate) { |
| 88 | for (const item of items) { |
| 89 | if (predicate(item)) return item; |
| 90 | } |
| 91 | return null; |
| 92 | } |
note
Choosing the right loop and using it correctly makes your code cleaner, safer, and more maintainable. Here are the best practices for JavaScript loops and iteration.
best practice
Choosing the Right Iteration Tool
| 1 | // Decision guide for iteration tools |
| 2 | |
| 3 | const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; |
| 4 | |
| 5 | // When you need to TRANSFORM each element |
| 6 | const doubled = data.map(n => n * 2); |
| 7 | // Result: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20] |
| 8 | |
| 9 | // When you need to FILTER elements |
| 10 | const evens = data.filter(n => n % 2 === 0); |
| 11 | // Result: [2, 4, 6, 8, 10] |
| 12 | |
| 13 | // When you need to REDUCE to a single value |
| 14 | const sum = data.reduce((acc, n) => acc + n, 0); |
| 15 | // Result: 55 |
| 16 | |
| 17 | // When you need to FIND a single element |
| 18 | const firstOver5 = data.find(n => n > 5); |
| 19 | // Result: 6 |
| 20 | |
| 21 | // When you need to CHECK if some/every element matches |
| 22 | const hasEven = data.some(n => n % 2 === 0); // true |
| 23 | const allPositive = data.every(n => n > 0); // true |
| 24 | |
| 25 | // When you need side effects with break support |
| 26 | for (const n of data) { |
| 27 | if (n > 7) break; // early exit supported |
| 28 | console.log(n); |
| 29 | } |
| 30 | |
| 31 | // When you need the index |
| 32 | for (const [i, n] of data.entries()) { |
| 33 | console.log(`data[${i}] = ${n}`); |
| 34 | } |
| 35 | |
| 36 | // When you need both key and value on an object |
| 37 | const config = { theme: "dark", lang: "en", debug: false }; |
| 38 | for (const [key, value] of Object.entries(config)) { |
| 39 | console.log(`${key}: ${value}`); |
| 40 | } |
| 41 | |
| 42 | // When you need a custom iteration pattern |
| 43 | function* chunked(array, size) { |
| 44 | for (let i = 0; i < array.length; i += size) { |
| 45 | yield array.slice(i, i + size); |
| 46 | } |
| 47 | } |
| 48 | for (const chunk of chunked(data, 3)) { |
| 49 | console.log(chunk); // [1,2,3], [4,5,6], [7,8,9], [10] |
| 50 | } |