JavaScript Loops — Infinite While That Crashed a Dashboard
CPU at 100% on a single core, event loop lag >10s—from a while loop missing its increment.
20+ years shipping production JavaScript and front-end systems at scale. Written from production experience, not tutorials.
- A loop repeats a block of code until a condition is false or all items are processed.
for: Use when you know the exact count (e.g., iterate an array with index).while: Use when the condition depends on runtime state (e.g., waiting for user input).forEach/for...of: Cleaner array iteration, butforEachlacksbreak/continue.- Performance:
foris fastest in tight loops;forEachhas function call overhead (~50ns per item). - Production trap: An infinite
whileloop freezes the event loop — always ensure the condition changes. - Biggest mistake: Using
<=instead of<in array loops — leads toundefinedaccess.
JavaScript loops are control flow structures that repeatedly execute a block of code until a specified condition evaluates to false. They exist because real-world programming constantly requires processing collections, waiting for conditions, or performing repetitive tasks — and writing the same code manually is both impractical and error-prone.
The core problem loops solve is automation of repetition, but they introduce a critical risk: if the termination condition is never met, you get an infinite loop that freezes the browser tab, crashes the dashboard, or exhausts server memory. This is not a theoretical edge case — it's a production incident waiting to happen, especially in event-driven environments like React or Node.js where a single runaway loop can block the entire event loop and take down the application.
In the JavaScript ecosystem, loops are not a monolith. You have the classic for loop for when you know the exact iteration count (e.g., iterating over a fixed array length), while for when you're waiting on a condition that may change unpredictably (e.g., polling an API until a status flips), and do...while for guaranteeing at least one execution before checking the condition.
Modern alternatives like forEach and for...of provide cleaner, less error-prone syntax for arrays and iterables, while for...in is best avoided for arrays due to its prototype-chain traversal and non-deterministic order — use it only for enumerating object keys when you understand the pitfalls. The choice matters: using while where a for would do increases the chance of an infinite loop, and using forEach on a massive dataset can blow the call stack.
When not to use loops? If you're processing data that can be expressed as transformations (map, filter, reduce), prefer those higher-order functions — they're declarative, testable, and avoid manual index management. For asynchronous iteration (e.g., reading files line by line), for-await-of with async iterables is safer than hand-rolling a while with promises.
And if you're building a real-time dashboard that polls every second, never use a blocking while(true) — use setInterval or requestAnimationFrame to yield control back to the event loop. The infinite loop that crashed your dashboard likely came from a while condition that never changed, or a for loop with a mutated collection that never reached its end.
Understanding these tradeoffs is what separates production-grade code from a ticket in the incident tracker.
Imagine you're stuffing 100 envelopes for a birthday party. You wouldn't write a separate instruction for each envelope — you'd write one instruction: 'Stuff an envelope, seal it, repeat until all 100 are done.' That's exactly what a loop is in JavaScript. It's one set of instructions that the computer repeats automatically until a condition is met, saving you from writing the same code over and over again.
Every app you've ever used relies on loops. When Instagram loads your feed, a loop runs through every post and renders it on screen. When Spotify builds your playlist, a loop processes every song. When a game checks if you've been hit by an enemy, a loop is running dozens of times per second. Loops are not just a feature of JavaScript — they are one of the fundamental building blocks of everything a program does.
Here's what most tutorials don't tell you: picking the wrong loop can cost you real production issues — from frozen browser tabs to silent data corruption. Understanding when to use each variant is what separates a script from a robust application.
Why JavaScript Loops Are Not Just Iteration
A loop in JavaScript is a control structure that repeatedly executes a block of code while a specified condition evaluates to true. The core mechanic is the condition check: before each iteration, the engine evaluates the condition; if truthy, the body runs; if falsy, the loop terminates. This is true for while, do-while, and for loops — the condition is the gate.
What matters in practice: the condition is evaluated fresh each iteration. If the condition never becomes false, you get an infinite loop. JavaScript is single-threaded, so an infinite loop blocks the event loop entirely — no UI updates, no network responses, no timers. The browser will eventually kill the tab or prompt the user. The only escape is a break statement, a return, or an exception.
Use loops when you need to process a collection or repeat work until a state changes. In real systems, loops are the backbone of polling, retry logic, and data processing. But they are also the most common source of accidental denial-of-service: a missing increment, a wrong comparison, or a mutated collection can freeze a dashboard for thousands of users.
The for Loop — When You Know Exactly How Many Times to Repeat
The for loop is the most common loop you'll write. You reach for it when you know upfront how many times something needs to happen — print 5 messages, process 10 orders, light up 7 checkboxes. Think of it like a strict gym trainer who says: 'Do exactly 10 reps, then stop.' No more, no less.
A for loop has three parts packed into one line, separated by semicolons. First, a starting point — you create a counter variable, usually called i, and set it to 0 (because JavaScript lists start counting from 0, not 1). Second, a condition — the loop keeps running as long as this condition is true. Third, an update — after each loop, you change the counter, usually by adding 1.
When the condition becomes false, JavaScript exits the loop and moves on. That's the whole mechanic. Everything else is just variations on this pattern.
Production reality: The for loop is the only loop that gives you full control — you can decrement, skip steps, or iterate backwards. It's also the fastest loop in JavaScript because it avoids any function call overhead. Use it when performance is critical, like in rendering loops or when processing large arrays.
// Imagine you're a barista making 5 coffees in a row. // You don't write five separate instructions — you loop. const totalCoffees = 5; for (let coffeeNumber = 1; coffeeNumber <= totalCoffees; coffeeNumber++) { // This block runs once for each coffee // coffeeNumber starts at 1, goes up by 1 each time, stops after 5 console.log(`Making coffee number ${coffeeNumber} of ${totalCoffees}`); } console.log("All coffees done! Time for a break."); // --- // Now let's loop through an actual array of items — a very common real-world use. const studentNames = ["Alice", "Bob", "Carmen", "David"]; for (let index = 0; index < studentNames.length; index++) { // studentNames.length is 4, so index goes 0, 1, 2, 3 // When index reaches 4, the condition (4 < 4) is FALSE, so the loop stops console.log(`Student ${index + 1}: ${studentNames[index]}`); }
< array.length for array iteration; use <= only for fixed-count loops.<. Fixed count? Use <=.The while Loop — When You Don't Know How Many Times to Repeat
The while loop is what you use when you can't predict upfront how many times you'll need to repeat something. The loop just keeps going as long as a condition stays true, and stops the moment it becomes false.
A great real-world analogy: imagine you're fishing. You don't say 'I'll cast exactly 7 times.' You say 'I'll keep casting until I catch a fish.' You don't know if that's 2 casts or 200. That uncertainty is exactly when while is the right tool.
In code, while loops are common for things like: keep asking the user for a password until they get it right, keep downloading data until the file is complete, or keep running a game loop until the player loses. The key discipline with while loops is that something inside the loop must eventually make the condition false — otherwise you get an infinite loop, and your program freezes forever.
Performance note: Because the condition is re-evaluated each iteration, while loops can be slightly slower than for loops if the condition involves a complex expression. But in practice, the overhead is negligible for most applications.
// Scenario: A vending machine keeps accepting coins until you've // inserted enough money for a snack that costs 75 cents. const snackPrice = 75; // in cents let totalInserted = 0; // start with no coins inserted let coinCount = 0; // track how many coins were put in // The condition: keep looping while we haven't paid enough while (totalInserted < snackPrice) { const coinValue = 25; // pretend every coin is a quarter (25 cents) totalInserted += coinValue; // add the coin's value to the running total coinCount++; // count one more coin inserted console.log(`Inserted coin #${coinCount}: total is now ${totalInserted} cents`); } // We only reach this line AFTER the condition (totalInserted < 75) becomes false console.log(`\nYou inserted ${coinCount} coins. Dispensing your snack! Enjoy!`); // --- // Another example: countdown timer let secondsRemaining = 5; while (secondsRemaining > 0) { console.log(`Launching in ${secondsRemaining}...`); secondsRemaining--; // THIS IS CRITICAL — without this, secondsRemaining never changes and we loop forever } console.log("🚀 Liftoff!");
let safe = 0; while (cond && safe++ < 1e6) { ... }.forEach and for...of — The Modern, Readable Way to Loop Arrays
Once you understand for loops, JavaScript gives you two cleaner tools specifically designed for looping through arrays and collections: forEach and for...of. They exist because the classic for loop, while powerful, has a lot of visual noise — the counter, the condition, the increment. When all you want to do is 'go through every item in this list,' there's a simpler way.
forEach is a method that lives on every array. You hand it a function, and it calls that function once for each item, automatically passing in the item, its index, and the whole array if you need them. It's expressive — reading it out loud almost sounds like plain English: 'for each order in orders, do this thing.'
for...of is even simpler — it's a loop syntax (like for) but designed for collections. You get the value directly without index gymnastics. Use for...of when you just need the values. Use forEach when you want the built-in index parameter without extra setup. Neither can be 'broken out of' easily with break — use a classic for loop if you need to exit early.
Performance reality: forEach incurs a function call overhead (~50ns per item on V8). For arrays under 10,000 items, it's unnoticeable. For performance-critical hot paths, a for loop is still faster. for...of is also slightly slower than a for loop but more readable.
const pizzaOrders = [ { customer: "Marco", topping: "Pepperoni" }, { customer: "Yuki", topping: "Mushrooms" }, { customer: "Priya", topping: "Extra Cheese" }, { customer: "Jordan", topping: "Jalapeños" } ]; // --- forEach --- // The function receives each item in the array, one at a time. // 'order' is just the name we give it — you can name it anything. // 'orderIndex' is automatically provided — it's the position (0, 1, 2...) console.log("=== forEach Example ==="); pizzaOrders.forEach(function(order, orderIndex) { console.log(`Order #${orderIndex + 1}: ${order.customer} wants ${order.topping} pizza`); }); // --- for...of --- // Cleaner syntax when you only need the value, not the index. // It reads almost like plain English: "for each order of pizzaOrders" console.log("\n=== for...of Example ==="); for (const order of pizzaOrders) { // 'order' holds the current item on each iteration console.log(`Preparing ${order.topping} pizza for ${order.customer}`); } // --- Arrow function shorthand (you'll see this everywhere in real codebases) --- console.log("\n=== Arrow Function Shorthand ==="); pizzaOrders.forEach((order) => { console.log(`Ready: ${order.customer}'s ${order.topping} pizza 🍕`); });
break, continue, or return to exit early.return inside — that return exits only the callback, not the loop.if (condition) return; in forEach expecting early exit, but the loop continues.break and continue; forEach does not.break.do...while — The Loop That Always Runs at Least Once
There's one more loop worth knowing: do...while. It's the least common, but it solves a specific problem elegantly. Every other loop checks its condition before running the code block. A do...while checks the condition after. That means the block always executes at least one time, even if the condition starts out false.
When does that matter? Think of a login form. You always want to show the form at least once — you check if the login was successful after the user submits it, not before they've even seen it. Or think of a game's main menu: show it first, then decide whether to keep showing it based on what the player picks.
In practice, do...while is genuinely rare in modern JavaScript. You'll mostly see it in interview questions and legacy code. But understanding it deepens your grasp of how loop conditions work, which makes you sharper with all the other loops too.
Production note: Because the loop body always runs once, do...while is useful for initialization logic that must execute before the condition check. For example, sending an initial request and then deciding whether to retry based on the response.
// Scenario: A quiz app that always asks at least one question, // then checks if the player wants to continue. // We'll simulate the user's responses with an array const simulatedUserResponses = ["yes", "yes", "no"]; // 'no' means they quit let responseIndex = 0; let questionNumber = 0; do { questionNumber++; // In a real app, this would be an actual quiz question console.log(`Question ${questionNumber}: What is 2 + 2? (Simulated answer: 4)`); // Simulate getting a response from the user const userWantsToContinue = simulatedUserResponses[responseIndex]; responseIndex++; console.log(`Player said: "${userWantsToContinue}"`); // The condition is checked DOWN HERE — after the block already ran if (userWantsToContinue !== "yes") { console.log("\nPlayer chose to stop. Thanks for playing!"); break; // exit the loop } } while (responseIndex < simulatedUserResponses.length); console.log(`Total questions answered: ${questionNumber}`); // Key point: even if simulatedUserResponses was empty and the condition // was false from the start, the block inside 'do' would still run once.
The for...in Loop — When to Use and When to Avoid It
JavaScript also provides a for...in loop, but it's designed for iterating over object keys, not arrays. It loops through all enumerable properties of an object, including inherited ones from the prototype chain. This makes it unpredictable for arrays because it returns keys as strings and includes array methods if any have been added to Array.prototype.
The rule of thumb: Never use for...in to iterate arrays. Use it only for plain objects when you need to loop over property names. For arrays, stick with for, for...of, or forEach.
Why it's dangerous for arrays: The iteration order is not guaranteed to be numeric index order. If you or a library has extended Array.prototype, those methods will appear as keys. This leads to subtle bugs that are hard to track down.
// DO NOT DO THIS - for...in on arrays is unreliable const arr = ['a', 'b', 'c']; Array.prototype.customMethod = function() {}; for (const key in arr) { console.log(key, arr[key]); // Output: "0 a", "1 b", "2 c", "customMethod undefined" } // CORRECT: Use plain object iteration const user = { name: 'Alice', role: 'admin', age: 30 }; for (const prop in user) { if (user.hasOwnProperty(prop)) { console.log(`${prop}: ${user[prop]}`); } } // Output: "name: Alice", "role: admin", "age: 30"
for...in on an array that had a polyfilled Array.prototype.includes. The loop iterated 'includes' as a key, causing the app to crash.for...of or forEach for arrays.for...in for objects, guard with hasOwnProperty.for...in only for iterating object keys, not arrays.hasOwnProperty to skip prototype properties.for...of or forEach for array iteration.Nested Loops: The Performance Trap Most Devs Ignore Until Production Burns
Nested loops aren't inherently bad. But they're the #1 cause of O(n²) runtime in JavaScript apps you didn't mean to write. Every inner iteration multiplies the outer loop's cost. That's fine for a 10-item config array. It's a disaster when your API response grows to 10,000 records and your UI freezes for 3 seconds. WHY: Because most nested loops are lazy — someone grabbed an array inside an array and looped both without asking if a map or a Set lookup could flatten the cost. The fix isn't always 'avoid nested loops.' It's 'know your data shape.' If you're comparing or filtering two lists, build a lookup table first. That turns O(n*m) into O(n+m). You'll feel it on the first paginated request. Here's the rule: before you nest, ask 'Can I pre-index one collection with a Map or Set?' If yes, do that. Your CPU and your users will thank you.
// io.thecodeforge — javascript tutorial // BAD: Nested loop — O(n*m) when both are large const users = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Charlie' } ]; const orders = [ { userId: 1, product: 'Laptop' }, { userId: 3, product: 'Mouse' }, { userId: 2, product: 'Keyboard' } ]; const result = []; for (let i = 0; i < users.length; i++) { for (let j = 0; j < orders.length; j++) { if (users[i].id === orders[j].userId) { result.push({ name: users[i].name, product: orders[j].product }); } } } console.log(result); // Output: [ { name: 'Alice', product: 'Laptop' }, { name: 'Bob', product: 'Keyboard' }, { name: 'Charlie', product: 'Mouse' } ] // GOOD: Build a Map lookup — O(n+m) const userMap = new Map(); for (const user of users) { userMap.set(user.id, user.name); } const optimized = []; for (const order of orders) { if (userMap.has(order.userId)) { optimized.push({ name: userMap.get(order.userId), product: order.product }); } } console.log(optimized); // Output: [ { name: 'Alice', product: 'Laptop' }, { name: 'Charlie', product: 'Mouse' }, { name: 'Bob', product: 'Keyboard' } ]
Loop Control Statements: Break and Continue Are Get Out of Jail Free Cards—Use Them Right
Break and continue aren't fancy syntax. They're your escape hatches when a loop would otherwise waste cycles or crash. But devs misuse them constantly. They throw a break in a forEach and wonder why it doesn't work. Or they use continue to skip items when a simple filter would've been cleaner. WHY: break stops the entire loop. That's your 'found it, stop searching' signal. Use it when you're looking for a single item in an unsorted list. continue skips the current iteration and moves to the next. That's your 'skip this one, keep going' signal. Use it when you're processing a list but need to ignore certain values (nulls, empty strings, banned users). The catch: these only work in for, while, do...while loops. forEach is a callback; return inside it just skips the current callback—it doesn't stop the loop. That's a bug that costs hours to find. Always use for...of if you need break or continue on an array. Here's the litmus test: if you need to abort early, use a for loop. If you need to skip items, ask yourself if .filter() + .map() says the same thing in half the lines. If yes, use that. If not, continue is fine.
// io.thecodeforge — javascript tutorial // Scenario: Find the first active admin user and stop const users = [ { name: 'Alice', role: 'user', active: true }, { name: 'Bob', role: 'admin', active: false }, { name: 'Charlie', role: 'admin', active: true }, { name: 'Diana', role: 'user', active: true } ]; let foundAdmin = null; // Use break to stop searching once found for (const user of users) { if (user.role !== 'admin' || !user.active) { continue; // Skip non-admin or inactive users } foundAdmin = user; break; // Exit loop — we found our one target } console.log(foundAdmin); // Output: { name: 'Charlie', role: 'admin', active: true } // Common mistake: break doesn't work in forEach const result = []; users.forEach(user => { if (user.role === 'admin') { result.push(user); // break; // This throws SyntaxError — forEach is a function, not a loop statement } });
Infinite Loops: The Silent Production Killer (And How to Arm Yourself)
An infinite loop doesn't crash immediately. It eats CPU. It freezes the browser tab. It spikes memory until the OS kills the process. By then, you've lost a user and maybe corrupted data. Every senior dev has written one. The difference is we know how to prevent them before they ship. WHY: Most infinite loops happen when your loop's exit condition never becomes false. Classic causes: forgetting to increment a counter in a while loop, using a reference type (like an object) as a condition that never changes, or a for loop where the termination check uses a variable that's only modified inside a conditional block. The fix is defensive: always verify your loop variable changes toward the exit condition on every iteration. If you're using while(true), you better have a break with a hard limit. For while loops with a dynamic condition, add a safety counter that aborts after a max iteration count. Here's the golden rule: any while loop that doesn't have a clear upper bound on iterations is a ticking bomb. If you can't prove it terminates in your head, add a guard.
// io.thecodeforge — javascript tutorial // DANGEROUS: Forgetting to update the iterator // This loop never ends because 'i' stays 0 let i = 0; // while (i < 10) { // console.log(i); // // no i++ here — infinite loop // } // SAFE: Always include a max iteration guard for dynamic loops function fetchWithRetry(url, maxRetries = 5) { let attempts = 0; let success = false; while (!success && attempts < maxRetries) { attempts++; console.log(`Attempt ${attempts} for ${url}`); // Simulating network request — assume it fails until attempt 3 if (attempts >= 3) { success = true; } } if (!success) { console.error('Failed after max retries'); } return success; } fetchWithRetry('/api/data'); // Output: Attempt 1 for /api/data // Attempt 2 for /api/data // Attempt 3 for /api/data // ULTRA SAFE: For while(true) scenarios, always have a kill switch let requestId = 0; const MAX_POLL_ATTEMPTS = 10; while (true) { requestId++; console.log(`Polling attempt ${requestId}`); if (requestId >= MAX_POLL_ATTEMPTS) { console.log('Polling timed out'); break; } // Real logic would check a condition to exit early if (/* simulated condition */ false) { break; } }
Break to a Label: The Escape Hatch for Nested Nightmares
Standard break only bails out of the innermost loop. When you're stuck three levels deep inside nested loops — parsing config trees or flattening multi-dimensional data — that single escape isn't enough. Labels give you an eject handle. You tag an outer loop with an identifier, then break labelName jumps directly out of that outer scope. No flags. No convoluted conditionals. No performance tax for keeping unnecessary iterations alive. This isn't a party trick. It's a production tool for when you control the nesting and need a clean exit from deep traversal. Most devs never touch labels because they over-engineer abstractions instead. But when you're debugging a hot loop in minified code, you'll wish you'd learned this. Use labels sparingly — they bypass normal control flow and can confuse junior eyes. But in the right spot, they save you from adding a found boolean and checking it every iteration.
// io.thecodeforge — javascript tutorial const matrix = [ [1, 2, 3], [4, 5, 6], [7, 8, 9], ]; let targetFound = null; searchOuter: for (let i = 0; i < matrix.length; i++) { for (let j = 0; j < matrix[i].length; j++) { if (matrix[i][j] === 5) { targetFound = { row: i, col: j }; break searchOuter; // exits both loops } } } console.log(targetFound);
Continue with a Label: Skip Iterations in the Right Loop
Everyone knows continue skips the rest of the current iteration and jumps to the next one. But inside nested loops, continue only applies to the innermost loop by default. That's fine when you want to skip one inner element. But what if the outer loop needs to jump ahead? That's where labeled continue shines. You tag the outer loop, then continue outerLabel tells JavaScript to skip the rest of the outer loop's current iteration — effectively moving to the next outer element immediately. This is gold when processing grouped data: an outer loop over user sessions, inner loop over events. If a session is corrupted, you want to bail on that whole session, not just one event. Without a label, you'd set a flag and break inner, then check flag and continue outer. With a label, it's one line. Cleaner code, fewer bugs, less mental overhead. Don't reach for this daily — but when you need it, it's the cleanest solution in the language.
// io.thecodeforge — javascript tutorial const sessions = [ { id: 'A', events: ['click', 'scroll', null] }, { id: 'B', events: ['hover', 'click'] }, { id: 'C', events: [null, null] }, ]; outerSession: for (let i = 0; i < sessions.length; i++) { for (let j = 0; j < sessions[i].events.length; j++) { if (sessions[i].events[j] === null) { console.log(`Skipping corrupted session ${sessions[i].id}`); continue outerSession; // skip to next session } } console.log(`Session ${sessions[i].id} clean`); }
continue is rarer than labeled break — and that's fine. Only use it when skipping an outer loop iteration is the clearest path. If you need more than one label in a function, refactor.continue lets you skip to the next iteration of an outer loop — ditch the flag-and-check pattern in nested loops.Frequently Asked Questions
When looping arrays, developers often ask: 'What's the difference between map() and forEach()?' Map returns a new array, forEach doesn't. 'Can I break out of forEach()?' No — forEach always runs every element. Use a for loop or some() instead. 'Why does my forEach callback run after loop finishes?' Because forEach is synchronous; any async operation inside a forEach callback will execute asynchronously after the loop completes. 'Should I use for...of or forEach for performance?' For most cases, for...of is slightly faster and supports break/continue. For production code processing large datasets, prefer for loops over methods to avoid callback overhead and unpredictable async behavior.
// io.thecodeforge — javascript tutorial // Demonstrate forEach break limitation const items = [1, 2, 3, 4, 5]; let found = false; // This doesn't work: break is illegal in forEach // try { // items.forEach(item => { // if (item === 3) break; // }); // } catch(e) { console.log('Error:', e.message); } // Proper alternative using some() items.some(item => { if (item === 3) { found = true; return true; // stops iteration } }); console.log(found); // true
some() or for loops when early termination is needed.What are the alternatives to forEach() due to async limitations?
forEach doesn't respect async/await — it fires all callbacks synchronously, so async operations run concurrently without waiting. For sequential async processing, use for...of with await inside (cleaner), or for loops with await (faster for large data). For parallel execution but need all results, use Promise.all() with map(). Never use forEach for async operations in production — it leads to race conditions, unhandled promise rejections, and silent failures. The golden rule: If you need sequential async, use for...of. If parallel, use map() + Promise.all(). If you must keep order but run in batches, combine reduce() with async/await pattern. Your future self (and your production logs) will thank you.
// io.thecodeforge — javascript tutorial // Async alternatives to forEach async function fetchUsers(ids) { // BAD: forEach won't await // ids.forEach(async id => await fetch(`/user/${id}`)); // GOOD: for...of with await (sequential) for (const id of ids) { await fetch(`/user/${id}`); } // FASTEST: Promise.all with map (parallel) const results = await Promise.all( ids.map(id => fetch(`/user/${id}`)) ); return results; } // Example usage console.log('Async alternatives ready');
map()). Never forEach.Summary
Choosing the right loop impacts both code readability and production performance. Use for loops when you know exact iterations, while for unknown counts, for...of for clean array iteration with break/continue support. Avoid for...in for arrays — it iterates enumerable properties and includes inherited ones, causing bugs. Nested loops must be optimized: reduce work inside inner loops, break early with labels when possible. forEach is synchronous and cannot be halted; never use it for async operations. For async scenarios, prefer for...of (sequential) or Promise.all() with map (parallel). Always guard against infinite loops with explicit exit conditions. The optimal loop choice depends on your data size, runtime constraints, and whether you need early termination — match the pattern to the problem, not the other way around.
// io.thecodeforge — javascript tutorial // Quick reference: loop choice guide const data = ['a', 'b', 'c']; // Use for...of for readability + break for (const item of data) { if (item === 'b') break; console.log(item); // 'a' } // Use for for performance with large data for (let i = 0; i < data.length; i++) { console.log(data[i]); } // Use while for unknown iterations let count = 0; while (count < 3) { console.log(count++); // 0,1,2 } console.log('Choose wisely');
The Infinite Loop That Brought Down the Dashboard
count < items.length was always true because count never increased. The loop ran indefinitely, blocking the event loop in Node.js (single-threaded).count++ inside the while loop. Also added a safety break: if (count > 10000) break; to guard against future logic errors. Implemented a maximum iteration guard in the code review checklist.- Always ensure the condition variable changes inside a while loop.
- Add a fail-safe iteration cap (e.g.,
if (iterations++ > MAX_SAFE) break;) for any unbounded loop. - Monitor event loop lag — it's the first signal of a blocked loop in production.
undefined when iterating<= instead of <? Check if index goes up to array.length - 1. Log the index and value at each iteration.break or continue. If you need early exit, switch to for or for...of. Also ensure the array is not null or undefined.console.log('loop iteration ' + i) to trace loop progression. If the log stops, the condition never turned false. Add an iteration counter with a max limit as a safety net.Add `console.count('loop')` inside the loop body.Check condition variable changes: `console.log('counter:', i)` after each iteration.if (i > 10000) break; above the loop body.Log the index: `console.log('index:', i, 'value:', array[i])`.Check loop condition: change `<=` to `<` if using index based loop.i <= array.length with i < array.length in the condition.Check if you can use `for...of` instead: `for (const item of array) { if (condition) break; }`.Alternatively, convert to a `for` loop with `return` as early exit (but `forEach` callback returns, not loop).array.forEach(...) with for (const item of array) { ... } if early break is needed.| Loop Type | Best Used When | Checks Condition | Works with break/continue | Readable for Arrays |
|---|---|---|---|---|
| for | You know the exact count upfront | Before each iteration | Yes | Works, but verbose |
| while | Condition depends on unpredictable state | Before each iteration | Yes | Possible, but unusual |
| do...while | Block must run at least once regardless | After each iteration | Yes | Rare in practice |
| forEach | Iterating every item in an array | N/A (array-driven) | No — use for instead | Excellent — most readable |
| for...of | Iterating values in any iterable collection | N/A (collection-driven) | Yes | Excellent — clean syntax |
| for...in | Iterating object keys (not arrays) | N/A (property enumeration) | Yes | Never use for arrays |
Key takeaways
Common mistakes to avoid
4 patternsOff-by-one error with array indexes
array[array.length] returns undefined. The last element is never processed, or the loop tries one too many.< array.length instead of <= array.length in the loop condition. Remember: arrays are zero-indexed, valid indices are 0 to length-1.Infinite loop due to missing variable update in while/do...while
Using `return` or `break` inside forEach expecting early exit
break causes a SyntaxError; return exits only the callback, not the loop.for loop or for...of if you need early exit. forEach is designed for processing all items; use it only when you intend to handle every element.Using for...in on arrays
for...of, forEach, or a classic for loop for arrays. Reserve for...in for plain objects, and add hasOwnProperty check.Interview Questions on This Topic
What is the difference between for...in and for...of in JavaScript, and why should you avoid for...in when iterating arrays?
Can you explain what causes an infinite loop and walk me through how you'd debug one in a real codebase?
<= versus < incorrectly in a for loop, or a logic error where the condition is always true. Debugging: use the browser devtools → Sources tab to pause execution, look at the call stack to see where the loop is stuck. Add console.log inside the loop to trace variable values. In Node.js, you can add a max iteration guard: if (++count > 100000) { console.log('breaking infinite loop'); break; } to safely stop.If forEach and a for loop can both iterate an array, why would you ever choose one over the other? Are there situations where forEach simply cannot be used?
break or continue, or when you need to iterate with a step other than 1 (e.g., every second element, or backwards). forEach cannot be used if you need to exit early, if you need to skip iterations with continue, or if you need to control the index in a non-sequential way. Also, forEach has slight performance overhead due to function calls, so for very large arrays in hot loops, a for loop is preferable.What does the `do...while` loop guarantee that `while` does not? Provide a real-world scenario.
do...while loop guarantees that the code block executes at least once, even if the condition is false initially. A while loop may never execute if the condition starts false. A real-world scenario: prompting a user for input — you always want to show the prompt at least once, then check if the input is valid. Another scenario: retry logic where you must attempt an operation at least once before deciding whether to retry based on the result.Frequently Asked Questions
The for loop and forEach are the most widely used. In modern JavaScript codebases, forEach (and its relatives map and filter) dominate array iteration because they're readable and expressive. The classic for loop is preferred when you need precise control, like breaking out early or iterating backwards.
for...in iterates over the keys (property names) of an object, while for...of iterates over the values of an iterable like an array or string. Never use for...in on arrays — it can pick up inherited prototype properties and its iteration order isn't guaranteed, which leads to subtle bugs. Use for...of for arrays and for...in for plain objects.
Use the break keyword inside a for, while, do...while, or for...of loop to exit immediately when a condition is met. If you want to skip the current iteration and jump to the next one without exiting, use continue instead. Note that forEach does not support break or continue — you must use a classic loop if you need early exit.
A classic for loop (with cached length) is generally the fastest because it avoids function call overhead. for...of is slightly slower, and forEach is slower due to the callback invocation. However, for most applications with arrays under 10,000 items, the difference is negligible — readability is more important. Use for loops in performance-critical hot paths like game loops or real-time data processing.
20+ years shipping production JavaScript and front-end systems at scale. Written from production experience, not tutorials.
That's JS Basics. Mark it forged?
12 min read · try the examples if you haven't