Loops: for, while, for...of
Pick the right loop in JavaScript, including for, while, do...while, for...of, for...in, break, continue and labels.
What you will learn
- Write classic for, while and do...while loops
- Use for...of to walk arrays, strings, maps and other iterables
- Explain why for...in is for object keys and not for arrays
- Use break, continue and labels to control a loop
On this page
Loops repeat work. The idea and the common patterns are in the fundamentals lesson on loops. JavaScript has several loop forms. In practice you choose between four of them, and this lesson shows when.
The classic for loop
A for loop has three parts in its brackets: an initialiser, a condition checked before each pass, and an update run after each pass.
for (let i = 0; i < 3; i++) { console.log("i is", i);}
for (let i = 10; i > 0; i -= 4) { console.log("countdown", i);}
let sum = 0;for (let i = 1, j = 10; i < j; i++, j--) { sum += i * j;}console.log(sum);i is 0i is 1i is 2countdown 10countdown 6countdown 2110Declare the counter with let. Each pass gets its own copy, as the previous lesson showed, and the counter does not exist after the loop. Any of the three parts can be a longer expression, as the last loop shows with two counters moving towards each other. It adds 1*10, 2*9, 3*8, 4*7 and 5*6, which is 110.
Use a classic for when you need the index or an unusual step. For plain “every item” loops, for...of is shorter and cannot go wrong at the ends.
while and do…while
while checks its condition before each pass, so it may run zero times. Use it when you do not know in advance how many passes you need.
do...while checks after the pass, so the body always runs at least once.
let n = 27;let steps = 0;
while (n !== 1) { n = n % 2 === 0 ? n / 2 : 3 * n + 1; steps++;}
console.log("steps:", steps);steps: 111This loop follows the Collatz sequence from 27: halve an even number, otherwise triple it and add one. It counts 111 steps to reach 1. The number of passes is not known in advance, which is why while fits.
do...while always runs its body once:
let attempts = 0;do { attempts++; console.log("attempt", attempts);} while (attempts < 3);
let neverRuns = 0;do { neverRuns++;} while (false);console.log(neverRuns);attempt 1attempt 2attempt 31The second do...while has a condition that is always false, yet its body ran once, so neverRuns ends as 1.
Every while needs something inside that eventually makes the condition false. A condition that never changes is an infinite loop. In a terminal, press Ctrl+C to stop it.
for…of
for...of takes each value from an iterable. Arrays (ordered lists written in square brackets, covered in the arrays lesson) and strings are iterable, and so are maps and sets, which come later.
const colours = ["red", "green", "blue"];for (const colour of colours) { console.log(colour);}
for (const char of "héllo") { console.log(char);}redgreenbluehéllo- Over an array you get the values, in order.
- Over a string you get one character at a time. It steps by code point, so a character outside the basic range, such as an emoji, comes through as one item even though it counts as two in
length. The strings lesson explains why. - A plain object is not iterable. Writing
for (const x of { a: 1 })stops withTypeError: {(intermediate value)} is not iterable. To loop over an object, usefor...inbelow, orObject.keys.
Write the loop variable as const unless you assign to it inside the body. A new one is created for each pass.
for…in
for...in visits the property names of an object, as strings.
const point = { x: 3, y: 4 };
for (const key in point) { console.log(key, point[key]);}x 3y 4point[key] reads the property whose name is stored in key. A plain point.key would look for a property literally called key.
It has two traps. On an array it gives the indexes as strings ("0", "1"), not numbers and not the values. And it also visits properties the object inherits, which surprises people once they use prototypes, an intermediate topic.
So use for...of for arrays. For an object’s own keys, loop over Object.keys(point) with for...of. Reach for for...in only if you specifically want inherited enumerable properties.
break, continue and labels
break leaves the loop immediately. continue skips the rest of the current pass and moves on to the next.
for (let i = 1; i <= 10; i++) { if (i % 2 === 0) continue; if (i > 7) break; console.log(i);}1357The loop skips even numbers with continue and stops at the first number above 7 with break.
Inside nested loops a plain break leaves only the inner loop. A label names a loop so that break or continue can target an outer one.
const grid = [ [1, 2, 3], [4, 5, 6], [7, 8, 9],];let found = null;
search: for (const row of grid) { for (const value of row) { if (value * value > 20) { found = value; break search; } }}
console.log("first value whose square exceeds 20:", found);first value whose square exceeds 20: 5The program looks through the grid, and as soon as a value has a square above 20, it remembers that value and leaves both loops. Labels are rare. A function that returns early is often a cleaner way to leave nested loops.
Choosing a loop
| You want to… | Use |
|---|---|
| visit every item of an array or string | for...of |
| count, or need the index or a custom step | classic for |
| repeat until a condition changes, with unknown length | while |
| run the body once before testing | do...while |
| walk an object’s own keys | for...of with Object.keys, or for...in |
Common pitfalls
- Off-by-one errors.
i <= array.lengthruns one pass too many and readsundefined. The usual bound isi < array.length. - Using
for...inon arrays. You get string indexes and possibly extra properties. Usefor...of. - Changing an array while looping over it. Adding or removing items shifts the positions of the others. Loop over a copy, or build a new array.
- Floating-point counters.
for (let x = 0; x !== 1; x += 0.1)never ends, becausexnever lands exactly on1. Count with integers and divide. - A
whilecondition that never changes. Check that the body moves towards the exit.
Exercises
Write a loop that plays a guessing game against a fixed secret number (37). Give it a list of guesses, [50, 25, 37, 10]. For each guess, print whether it is too high, too low or right, and stop when it is right. The guess after the correct one must not be tried.
Input: none. Expected output: Guess 1: 50 is too high, Guess 2: 25 is too low, Guess 3: 37 is right.
Show solution
const secret = 37;const guesses = [50, 25, 37, 10];
let tries = 0;for (const guess of guesses) { tries++; if (guess === secret) { console.log(`Guess ${tries}: ${guess} is right`); break; } const hint = guess < secret ? "low" : "high"; console.log(`Guess ${tries}: ${guess} is too ${hint}`);}Guess 1: 50 is too highGuess 2: 25 is too lowGuess 3: 37 is rightfor...of walks the guesses, tries counts them, and break leaves the loop when the guess is right, so 10 is never tried. The conditional operator picks “low” or “high” inside the template literal.
Print a 5 by 5 multiplication table with the numbers lined up in columns four characters wide. Use nested loops and padStart from the strings lesson.
Input: none. Expected output: five lines, starting 1 2 3 4 5 and ending 5 10 15 20 25.
Show solution
const size = 5;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 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 5 10 15 20 25The outer loop picks the row and the inner loop builds one line, one cell at a time. String(row * col) turns the number into text, and padStart(4) pads it with spaces on the left to width 4. Each line is printed once its inner loop is done.
Loop from 1 to 15. Print Fizz for multiples of 3, Buzz for multiples of 5, FizzBuzz for multiples of both, and the number itself otherwise.
Input: none. Expected output: 15 lines, starting 1, 2, Fizz, 4, Buzz and ending with FizzBuzz for 15.
Show solution
for (let i = 1; i <= 15; i++) { if (i % 15 === 0) { console.log("FizzBuzz"); } else if (i % 3 === 0) { console.log("Fizz"); } else if (i % 5 === 0) { console.log("Buzz"); } else { console.log(i); }}12Fizz4BuzzFizz78FizzBuzz11Fizz1314FizzBuzzThe test for 15 goes first, because a multiple of 15 would otherwise be caught by the test for 3. The order of an else if chain matters.
Use a while loop to add the numbers from 1 to 100 and print the total.
Input: none. Expected output: 5050.
Show solution
let number = 1;let total = 0;
while (number <= 100) { total += number; number++;}
console.log(total);5050number++ is what moves the loop towards its end. Without it the condition would stay true forever.
Summary
- Use
for...ofto visit the items of arrays and strings. Use a classicforwhen you need the index or a special step, andwhilewhen the number of passes is not known. do...whilealways runs the body once.for...ingives property names as strings. Do not use it on arrays.breakleaves a loop,continueskips to the next pass, and a label lets either one target an outer loop.
Next: raising and handling errors with throw, try, catch and finally.