Skip to main content

How to Use Loops in JavaScript: while, for, break, continue, for...of, for...in and More

Programs constantly need to repeat actions. Process every item in a shopping cart. Check each character in a password. Retry a failed network request. Count down from ten. Without loops, you would need to write the same code hundreds or thousands of times. Loops let you write the code once and have the computer repeat it as many times as needed.

JavaScript provides several loop constructs, each suited to different situations. This guide covers every loop type in depth, from the fundamental while and for loops to the modern for...of iterator. You will learn how to control loop execution with break and continue, how to handle nested loops with labels, and how to avoid the most common loop-related bugs that waste developers' debugging time.

The while Loop

The while loop is the simplest loop structure. It repeats a block of code as long as a condition remains truthy.

Syntax

while (condition) {
// code to repeat
}

Before each iteration, the condition is evaluated. If it is truthy, the loop body executes. If it is falsy, the loop ends and execution continues with the code after the loop.

Basic Example

let count = 1;

while (count <= 5) {
console.log(count);
count++;
}
// Output: 1, 2, 3, 4, 5

console.log("Loop finished. Count is now:", count); // 6

Step-by-step execution:

Iteration 1: count=1, 1 <= 5 is true  → log 1, count becomes 2
Iteration 2: count=2, 2 <= 5 is true → log 2, count becomes 3
Iteration 3: count=3, 3 <= 5 is true → log 3, count becomes 4
Iteration 4: count=4, 4 <= 5 is true → log 4, count becomes 5
Iteration 5: count=5, 5 <= 5 is true → log 5, count becomes 6
Check: count=6, 6 <= 5 is false → loop ends

When to Use while

Use while when you do not know in advance how many iterations you need:

// Roll a die until you get a 6
let rolls = 0;
let result;

while (result !== 6) {
result = Math.floor(Math.random() * 6) + 1;
rolls++;
console.log(`Roll ${rolls}: ${result}`);
}
console.log(`Got a 6 after ${rolls} rolls!`);
// Process user input until they type "quit"
let input = "";

while (input !== "quit") {
input = prompt("Enter a command (type 'quit' to exit):");
if (input !== "quit") {
console.log(`Processing: ${input}`);
}
}
console.log("Goodbye!");

Single-Line Body

Like if statements, single-line while bodies do not require braces, but you should always include them:

// Works but dangerous
while (count < 10) count++;

// Always use braces
while (count < 10) {
count++;
}

The do...while Loop

The do...while loop is a variation of while that always executes the body at least once before checking the condition.

Syntax

do {
// code to repeat (runs at least once)
} while (condition);

Notice the semicolon after the condition. This is required for do...while.

Basic Example

let count = 1;

do {
console.log(count);
count++;
} while (count <= 5);
// Output: 1, 2, 3, 4, 5

The Key Difference from while

With a regular while, the body might never execute if the condition is initially false. With do...while, the body always runs at least once:

// while: body never executes
let x = 10;
while (x < 5) {
console.log("This never runs");
}

// do...while: body executes once
let x = 10;
do {
console.log("This runs once!"); // Runs!
} while (x < 5);
// "This runs once!" is printed even though x (10) is not less than 5

Practical Use Cases

do...while is ideal when you need at least one execution, such as input validation:

let password;

do {
password = prompt("Create a password (minimum 8 characters):");
} while (password !== null && password.length < 8);

if (password === null) {
console.log("Registration cancelled");
} else {
console.log("Password accepted!");
}
// Menu system: always show the menu at least once
let choice;

do {
choice = prompt(
"Menu:\n1. View profile\n2. Settings\n3. Logout\n\nEnter your choice:"
);

switch (choice) {
case "1":
alert("Showing profile...");
break;
case "2":
alert("Opening settings...");
break;
case "3":
alert("Logging out...");
break;
default:
if (choice !== null) alert("Invalid choice, try again.");
}
} while (choice !== "3" && choice !== null);

The for Loop

The for loop is the most commonly used loop in JavaScript. It combines initialization, condition checking, and increment into a single, compact line.

Anatomy of a for Loop

for (initialization; condition; update) {
// loop body
}
PartPurposeExample
InitializationRuns once before the loop startslet i = 0
ConditionChecked before each iterationi < 5
UpdateRuns after each iterationi++

Basic Example

for (let i = 0; i < 5; i++) {
console.log(i);
}
// Output: 0, 1, 2, 3, 4

Execution flow:

1. Initialization: let i = 0
2. Condition check: 0 < 5? YES → run body (log 0)
3. Update: i++ (i becomes 1)
4. Condition check: 1 < 5? YES → run body (log 1)
5. Update: i++ (i becomes 2)
... continues ...
10. Condition check: 4 < 5? YES → run body (log 4)
11. Update: i++ (i becomes 5)
12. Condition check: 5 < 5? NO → loop ends

Common for Loop Patterns

Counting up:

for (let i = 1; i <= 10; i++) {
console.log(i); // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
}

Counting down:

for (let i = 10; i >= 1; i--) {
console.log(i); // 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
}
console.log("Liftoff!");

Stepping by values other than 1:

// Count by twos
for (let i = 0; i <= 20; i += 2) {
console.log(i); // 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20
}

// Count by fives
for (let i = 0; i <= 100; i += 5) {
console.log(i); // 0, 5, 10, 15, ... 95, 100
}

Iterating over an array by index:

let fruits = ["apple", "banana", "cherry", "date"];

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

Iterating backward over an array:

let colors = ["red", "green", "blue"];

for (let i = colors.length - 1; i >= 0; i--) {
console.log(colors[i]);
}
// blue, green, red

Optional Parts

Every part of the for loop header is optional. You can omit any or all of them:

// Omit initialization (variable declared elsewhere)
let i = 0;
for (; i < 5; i++) {
console.log(i);
}

// Omit update (done inside the body)
for (let i = 0; i < 5;) {
console.log(i);
i++;
}

// Omit everything (infinite loop, be careful!)
for (;;) {
// Runs forever unless you break out
break; // Prevents infinite loop
}

Multiple Variables in for Loop

You can initialize and update multiple variables using the comma operator:

for (let i = 0, j = 10; i < j; i++, j--) {
console.log(`i=${i}, j=${j}`);
}
// i=0, j=10
// i=1, j=9
// i=2, j=8
// i=3, j=7
// i=4, j=6

Breaking Out of Loops with break

The break statement immediately terminates the loop and continues with the code after the loop.

Basic Usage

for (let i = 0; i < 100; i++) {
if (i === 5) {
break; // Exit the loop immediately
}
console.log(i);
}
// Output: 0, 1, 2, 3, 4
// Note: 5 is NOT printed because break runs before console.log

Searching for a Value

let numbers = [4, 8, 15, 16, 23, 42];
let target = 16;
let foundIndex = -1;

for (let i = 0; i < numbers.length; i++) {
if (numbers[i] === target) {
foundIndex = i;
break; // No need to keep searching
}
}

console.log(foundIndex); // 3

break in while Loops

// Process items until a specific condition
let attempts = 0;

while (true) { // Intentionally infinite
attempts++;
let result = Math.random();

if (result > 0.9) {
console.log(`Success after ${attempts} attempts!`);
break; // Exit the infinite loop
}
}

This while (true) with break pattern is common when the exit condition is complex or occurs in the middle of the loop body rather than at the beginning.

break Only Exits the Innermost Loop

In nested loops, break only exits the loop it is directly inside:

for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (j === 1) {
break; // Only breaks the inner loop
}
console.log(`i=${i}, j=${j}`);
}
// Execution continues here after inner break
}
// i=0, j=0
// i=1, j=0
// i=2, j=0

To break out of outer loops, you need labels (covered below).

Skipping Iterations with continue

The continue statement skips the rest of the current iteration and jumps to the next one.

Basic Usage

for (let i = 0; i < 10; i++) {
if (i % 2 !== 0) {
continue; // Skip odd numbers
}
console.log(i);
}
// Output: 0, 2, 4, 6, 8

When continue executes:

  1. The rest of the loop body is skipped
  2. For for loops: the update expression runs, then the condition is checked
  3. For while loops: the condition is checked immediately

Practical Examples

// Process only valid items
let data = [10, -5, 20, null, 30, undefined, 40];

for (let i = 0; i < data.length; i++) {
if (data[i] == null || data[i] < 0) {
continue; // Skip null, undefined, and negative values
}
console.log(`Processing: ${data[i]}`);
}
// Processing: 10
// Processing: 20
// Processing: 30
// Processing: 40
// Skip specific indexes
let letters = ["a", "b", "c", "d", "e", "f"];

for (let i = 0; i < letters.length; i++) {
if (i === 2 || i === 4) {
continue; // Skip indices 2 and 4
}
console.log(letters[i]);
}
// a, b, d, f

continue vs. if Block

Using continue to skip iterations is equivalent to wrapping the remaining code in an if block, but continue can reduce nesting:

// Without continue: deeply nested
for (let i = 0; i < items.length; i++) {
if (items[i] !== null) {
if (items[i] > 0) {
if (items[i] !== skipValue) {
processItem(items[i]);
}
}
}
}

// With continue: flat and readable
for (let i = 0; i < items.length; i++) {
if (items[i] === null) continue;
if (items[i] <= 0) continue;
if (items[i] === skipValue) continue;

processItem(items[i]);
}
caution

continue cannot be used with the ternary operator. This is a syntax error:

// SyntaxError!
for (let i = 0; i < 5; i++) {
(i === 2) ? continue : console.log(i);
}

// Use an if statement instead
for (let i = 0; i < 5; i++) {
if (i === 2) continue;
console.log(i);
}

The ternary operator is an expression, and continue is a statement. Statements cannot appear where expressions are expected.

Labels for Nested Loops

Labels let you control which loop break or continue affects, which is essential when working with nested loops.

Syntax

labelName: for (...) {
for (...) {
break labelName; // Breaks the outer loop
continue labelName; // Continues the outer loop
}
}

Breaking Out of Nested Loops

Without labels, break only exits the innermost loop. Labels let you break out of any enclosing loop:

// Find the first pair that sums to a target
let numbers = [1, 5, 8, 12, 15, 20];
let target = 17;
let found = false;

outerLoop: for (let i = 0; i < numbers.length; i++) {
for (let j = i + 1; j < numbers.length; j++) {
if (numbers[i] + numbers[j] === target) {
console.log(`Found: ${numbers[i]} + ${numbers[j]} = ${target}`);
found = true;
break outerLoop; // Exits BOTH loops immediately
}
}
}

if (!found) {
console.log("No pair found");
}
// Output: Found: 5 + 12 = 17

Without break outerLoop, only the inner loop would exit, and the outer loop would continue searching unnecessarily.

Continuing an Outer Loop

// Skip processing entire rows if they contain invalid data
let matrix = [
[1, 2, 3],
[4, -1, 6], // Contains -1 (invalid)
[7, 8, 9],
[10, 11, -2], // Contains -2 (invalid)
];

rowLoop: for (let row = 0; row < matrix.length; row++) {
for (let col = 0; col < matrix[row].length; col++) {
if (matrix[row][col] < 0) {
console.log(`Skipping row ${row} (invalid data at column ${col})`);
continue rowLoop; // Skip to the next row
}
}
console.log(`Row ${row} is valid:`, matrix[row]);
}
// Row 0 is valid: [1, 2, 3]
// Skipping row 1 (invalid data at column 1)
// Row 2 is valid: [7, 8, 9]
// Skipping row 3 (invalid data at column 2)

Label Naming Conventions

Labels are typically written in camelCase and describe what they label:

// Common label names
outerLoop:
searchLoop:
rowLoop:
mainLoop:
info

Labels are rarely needed in practice. Most situations that seem to require labels can be refactored by extracting the nested loop into a separate function and using return to exit. However, labels are useful to know about for search algorithms and matrix operations.

// Alternative to labels: extract into a function
function findPair(numbers, target) {
for (let i = 0; i < numbers.length; i++) {
for (let j = i + 1; j < numbers.length; j++) {
if (numbers[i] + numbers[j] === target) {
return [numbers[i], numbers[j]]; // Return exits both loops
}
}
}
return null;
}

let pair = findPair([1, 5, 8, 12, 15, 20], 17);
console.log(pair); // [5, 12]

Infinite Loops: How to Avoid and Debug Them

An infinite loop runs forever because its exit condition is never met. This freezes the browser tab or crashes your Node.js process.

Common Causes

Cause 1: Forgetting to update the loop variable

// INFINITE LOOP!
let i = 0;
while (i < 5) {
console.log(i);
// Missing: i++ or i = i + 1
// i stays 0 forever, and 0 < 5 is always true
}

Cause 2: Updating the wrong variable

// INFINITE LOOP!
let i = 0;
let j = 0;
while (i < 5) {
console.log(i);
j++; // Oops! Should be i++
}

Cause 3: Condition that can never become false

// INFINITE LOOP!
for (let i = 10; i >= 0; i++) { // i++ goes up, i >= 0 is always true
console.log(i);
}
// Should be i-- to count down

Cause 4: Floating-point comparison

// INFINITE LOOP (potentially)!
let x = 0.1;
while (x !== 1.0) {
x += 0.1;
console.log(x);
}
// Due to floating-point precision, x may never exactly equal 1.0
// 0.1 + 0.1 + ... = 0.9999999999999999, not 1.0

// Fix: use less-than instead of not-equal
while (x < 1.0) {
x += 0.1;
}

How to Debug Infinite Loops

In the browser:

  1. The tab will become unresponsive
  2. Chrome shows a "Page Unresponsive" dialog. Click "Kill page"
  3. Alternatively, open Task Manager (Shift + Esc in Chrome) and kill the tab's process

In Node.js:

  1. The terminal will hang
  2. Press Ctrl + C to terminate the process

Prevention strategies:

// Add a safety counter during development
let i = 0;
let safetyCounter = 0;
const MAX_ITERATIONS = 10000;

while (someComplexCondition) {
// loop body
safetyCounter++;
if (safetyCounter > MAX_ITERATIONS) {
console.error("Loop exceeded maximum iterations!");
break;
}
}

Choosing the Right Loop for the Job

SituationRecommended LoopWhy
Know the exact number of iterationsforCleanest syntax for counted loops
Iterating over an array by indexforDirect index access
Iterating over array valuesfor...ofCleaner, no index management
Iterating over object propertiesfor...inDesigned for object keys
Unknown number of iterationswhileNatural for condition-based loops
Must execute at least oncedo...whileGuarantees first execution
Processing until a condition in the middlewhile (true) + breakExit from any point
Transforming every array element.map()Functional, returns new array
Filtering array elements.filter()Functional, returns filtered array
// Counted iteration → for
for (let i = 0; i < 10; i++) { }

// Array values → for...of
for (let fruit of fruits) { }

// Object keys → for...in
for (let key in user) { }

// Unknown iterations → while
while (hasMorePages()) { }

// At least once → do...while
do { input = getInput(); } while (!isValid(input));

for...of: Iterating Over Iterables (Preview)

The for...of loop (introduced in ES6) iterates over iterable objects such as arrays, strings, Maps, Sets, and more. It gives you the values directly, without needing to manage an index.

Arrays

let colors = ["red", "green", "blue"];

// Traditional for loop
for (let i = 0; i < colors.length; i++) {
console.log(colors[i]);
}

// for...of is cleaner, less error-prone
for (let color of colors) {
console.log(color);
}
// red, green, blue

Strings

for...of iterates over individual characters, correctly handling Unicode:

let greeting = "Hello!";

for (let char of greeting) {
console.log(char);
}
// H, e, l, l, o, !

// Works correctly with emoji (surrogate pairs)
let emoji = "Hi 👋🌍";
for (let char of emoji) {
console.log(char);
}
// H, i, (space), 👋, 🌍
// Each emoji is treated as one character, not split into surrogate pairs

When You Need the Index Too

If you need both the value and the index, use entries():

let fruits = ["apple", "banana", "cherry"];

for (let [index, fruit] of fruits.entries()) {
console.log(`${index}: ${fruit}`);
}
// 0: apple
// 1: banana
// 2: cherry

for...of Does Not Work on Plain Objects

let user = { name: "Alice", age: 30 };

// TypeError: user is not iterable
for (let value of user) {
console.log(value);
}

// Use Object.entries() to iterate over object key-value pairs
for (let [key, value] of Object.entries(user)) {
console.log(`${key}: ${value}`);
}
// name: Alice
// age: 30

for...in: Iterating Over Object Properties (Preview)

The for...in loop iterates over the enumerable property names (keys) of an object.

Basic Usage

let user = {
name: "Alice",
age: 30,
city: "New York"
};

for (let key in user) {
console.log(`${key}: ${user[key]}`);
}
// name: Alice
// age: 30
// city: New York

Why Not to Use for...in with Arrays

While for...in technically works on arrays, it is not recommended:

let colors = ["red", "green", "blue"];

for (let index in colors) {
console.log(typeof index); // "string" (not a number!)
console.log(index, colors[index]);
}
// "0" red
// "1" green
// "2" blue

// Problems with for...in on arrays:
// 1. Keys are strings, not numbers
// 2. It iterates ALL enumerable properties, including inherited ones
// 3. Iteration order is not guaranteed for numeric-like keys in all engines
// 4. It's 10-100x slower than for or for...of on arrays

The Rule

Arrays  → Use for...of or .forEach() or traditional for
Objects → Use for...in or Object.keys()/Object.entries()
warning

Never use for...in to iterate over arrays. Use for...of, .forEach(), or a traditional for loop instead. for...in is designed for objects and has several gotchas with arrays, including string keys, inherited properties, and unreliable ordering.

Common Mistakes: Off-by-One Errors, Infinite Loops, and Modifying Arrays During Iteration

Mistake 1: Off-by-One Errors

Off-by-one errors happen when a loop runs one time too many or one time too few. They are the most common loop bug.

let arr = ["a", "b", "c", "d", "e"];  // length is 5, indices 0-4

// BUG: <= causes index out of bounds
for (let i = 0; i <= arr.length; i++) {
console.log(arr[i]);
}
// a, b, c, d, e, undefined ← i=5 accesses arr[5] which doesn't exist
let arr = ["a", "b", "c", "d", "e"];  // length is 5, indices 0-4

// CORRECT: < (not <=) with array length
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
// a, b, c, d, e

The rule for arrays: use i < array.length (strictly less than), not i <= array.length.

// Another common off-by-one: starting at 1 instead of 0
for (let i = 1; i < arr.length; i++) {
console.log(arr[i]);
}
// b, c, d, e ← missed "a" at index 0!

// Or ending too early
for (let i = 0; i < arr.length - 1; i++) {
console.log(arr[i]);
}
// a, b, c, d ← missed "e" at index 4!

Prevention: Use for...of when you do not need the index. It automatically handles the bounds:

for (let item of arr) {
console.log(item);
}
// a, b, c, d, e (no chance of off-by-one)

Mistake 2: Modifying an Array During Iteration

Adding or removing elements while iterating with a for loop causes skipped or duplicated elements:

// BUG: removing elements during forward iteration
let numbers = [1, 2, 3, 4, 5, 6];

for (let i = 0; i < numbers.length; i++) {
if (numbers[i] % 2 === 0) {
numbers.splice(i, 1); // Remove even numbers
}
}

console.log(numbers); // [1, 3, 5, 6] (6 was NOT removed!)

What happened: When 2 (index 1) was removed, 3 shifted to index 1. The loop then moved to index 2, which was now 4, skipping 3. Later, when 4 (now at index 2) was removed, 5 shifted to index 2 and 6 shifted to index 3. The loop moved to index 3, which was now past the end, so 6 was skipped.

Fix 1: Iterate backward

let numbers = [1, 2, 3, 4, 5, 6];

for (let i = numbers.length - 1; i >= 0; i--) {
if (numbers[i] % 2 === 0) {
numbers.splice(i, 1);
}
}

console.log(numbers); // [1, 3, 5] (correct!)

Fix 2: Use filter() (creates a new array)

let numbers = [1, 2, 3, 4, 5, 6];
let oddNumbers = numbers.filter(n => n % 2 !== 0);
console.log(oddNumbers); // [1, 3, 5]

Mistake 3: Using var Instead of let in for Loops

// BUG with var: all callbacks share the same i
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 100);
}
// Output: 3, 3, 3 (NOT 0, 1, 2!)
// By the time setTimeout fires, the loop is done and i is 3

// FIX: use let (each iteration gets its own i)
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 100);
}
// Output: 0, 1, 2 (correct!)

Mistake 4: Comparing with !== Instead of < for Loop Bounds

// FRAGILE: depends on i hitting exactly 10
for (let i = 0; i !== 10; i += 3) {
console.log(i);
}
// 0, 3, 6, 9, 12, 15, ... INFINITE LOOP!
// i goes 0, 3, 6, 9, 12... it never equals exactly 10

// ROBUST: use < or <= for bounds
for (let i = 0; i < 10; i += 3) {
console.log(i);
}
// 0, 3, 6, 9 (stops correctly)

Mistake 5: Forgetting break in Search Loops

// WASTEFUL: continues searching after finding the result
let numbers = [10, 20, 30, 40, 50];
let found = null;

for (let i = 0; i < numbers.length; i++) {
if (numbers[i] === 30) {
found = numbers[i];
// Missing break! Loop continues checking 40 and 50 unnecessarily
}
}
// CORRECT: break immediately after finding
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] === 30) {
found = numbers[i];
break; // Stop searching
}
}

// BEST: use the built-in find method
let found = numbers.find(n => n === 30);

Mistake 6: Mutating the Loop Variable Accidentally

// BUG: i is modified inside the loop body
for (let i = 0; i < 5; i++) {
console.log(i);
if (i === 2) {
i = 0; // Resets the counter (infinite loop!)
}
}
// BUG: using the loop variable for something else
for (let i = 0; i < 10; i++) {
i = parseInt(someString); // Overwrites the loop counter
}

Use a different variable name if you need a separate counter or value inside the loop body.

Summary

Loops are essential tools for repeating actions in JavaScript:

  • while checks the condition before each iteration. Use it when you do not know how many iterations you need.
  • do...while runs the body at least once, then checks the condition. Use it for input validation and menus.
  • for combines initialization, condition, and update in one line. It is the standard choice for counted iterations and array traversal by index.
  • break immediately exits the loop. Use it for search operations and early termination.
  • continue skips the rest of the current iteration. Use it to filter out unwanted items without deep nesting.
  • Labels let break and continue target outer loops in nested structures. They are useful but rarely needed.
  • Infinite loops happen when the exit condition is never met. Always ensure your loop variable changes in a way that will eventually make the condition false.
  • for...of iterates over iterable values (arrays, strings, Maps, Sets). It is cleaner and safer than index-based for loops.
  • for...in iterates over object property keys. Never use it on arrays.
  • Watch out for off-by-one errors (use < not <= with array length), modifying arrays during iteration (iterate backward or use filter()), and var in loops (always use let).

With loops mastered, you are ready to explore the switch statement, which provides a clean alternative to long else if chains when comparing a single value against multiple options.