Loops: while & for
Repeat JavaScript code safely with while, do...while, for, break, continue, labels, and guardrails that prevent infinite loops.
- 01Choose the right loopPick
while,do...while, orforby asking one question: what do you know before the loop starts? - 02Follow a loop step by stepSay what runs when: the start, each check, each lap of the body, each update, and the final output.
- 03Stop loops safelyUse
break,continue, labels, and guards, so a loop never freezes a page.
Why loops exist
A loop runs the same block of code again and again. Each run is called a lap. Each time round, JavaScript checks a condition: a question whose answer is true or false. While the answer is true, the loop keeps going. When the answer is false, the loop stops.
Loops save you from copying code. To print ten lines, you don't write console.log ten times. You write it once, inside a loop, and the loop runs it ten times.
This lesson covers three kinds of loop:
whilechecks the condition first. Use it when you don't know how many laps you need.forkeeps a counter, likei, in one line at the top. Use it when you count laps.do...whileruns the body once first, then checks. Use it when the work must happen at least once.
Each chore has three things: a place to start, a question to ask ("Any shirts left?"), and a step that gets you closer to "no". You fold one shirt, so there is one fewer left. A loop needs the same three things.
- In real life: Fold a shirt, then look: are there more shirts?
- In JavaScript: A
whileloop: you don't know how many laps - In real life: Run five laps and count each one
- In JavaScript: A
forloop: a counter that starts, gets checked, and goes up - In real life: Taste the soup once, then decide if it needs salt
- In JavaScript: A
do...whileloop: do it once, then check
In this lesson you will step through real code, change loop settings, and sort tasks by loop type. You will also practice with sums, FizzBuzz, loops that run one lap too many, safety limits, and searches that stop early.
while and do...while
STEP THROUGHA while loop checks its condition before the body. The body is the code between the curly brackets { }. If the condition is already false at the start, the body runs zero times.
while (condition) {
// repeat this body while condition is true
}A do...while loop does it the other way round. It runs the body once, and then checks the condition. So the body always runs at least once. This is useful for "try once, then maybe try again" jobs, such as asking a user to type something.
do {
// runs at least once
} while (condition);while checks first, so its body may never run. do...while runs the body first, so it always runs at least once.Here is a small while loop that counts attempts. attempts starts at 0. The loop runs while attempts is less than 2, and each lap adds 1. After two laps, attempts is 2, the check gives false, and line 5 prints 2.
let attempts = 0;
while (attempts < 2) {
attempts++;
}
console.log(attempts);You don't know how many stirs the sauce needs. So you stir, look, and stir again while it is still thin. Soup is different: you can't know if it needs salt until you taste it once.
- In real life: Stir while the sauce is still thin
- In JavaScript:
while (thin) { stir(); } - In real life: Taste once, then add salt only if it needs more
- In JavaScript:
do { taste(); } while (needsSalt);
In this replay, both loops start at 3. The while check thick < 3 is false right away, so its body never runs. The do...while body still runs once and adds 1 before its first check.
Watch where each loop does its first check. Starting at 3, the while body runs zero times, but the do...while body still runs once.
script
while (thick < 3) { thick++;}console.log("while: " + thick);let taste = 3;do { taste++;} while (taste < 3);console.log("do: " + taste);A do...while loop ends with a semicolon after the condition: } while (condition);. A while loop and a for loop don't need a semicolon after their closing curly bracket.
for loops: counter, condition, update
INTERACTIVEA for loop puts everything that controls the loop in one line at the top, called the header. Most for loops use a counter: a variable that counts the laps, often called i. The header has three parts, split by semicolons. Read each row of the table as one part: its name, when it runs, and an example.
| Part | When does it run? | Example |
|---|---|---|
| Initializer: it sets up the counter. | It runs once, before the first check. | let i = 0 |
| Condition: it is the check. | It runs before every lap. | i < 3 |
| Update: it moves the counter. | It runs after every lap. | i++ |
Read for (let i = 0; i < 3; i++) like this:
- Start the counter
iat 0. - Keep going while
iis less than 3. - After each lap, add 1 to
i. That is whati++means.
The body runs with i equal to 0, then 1, then 2. Then i becomes 3. The check 3 < 3 gives false, so the loop stops.
let i = 0 runs once. Then the loop goes round: check, body, update, and back to the check. When the check gives false, the loop ends.Guess the printed total first. Then step through: set up the counter once, then check, body, update, and check again.
script
for (let i = 0; i < 3; i++) { total += i;}console.log(total);The timeline below shows the same order for a loop that adds 1, 2, and 3. Use it to learn the order by heart: the initializer once, then check, body, update, check again, and so on.
let total = 0;for (let i = 1; i <= 3; i++) { total += i;}console.log(total);- init total=0
- init i=1
- check i=1: true
- body total=1
- update i becomes 2
- check i=2: true
- body total=3
- update i becomes 3
- check i=3: true
- body total=6
- update i becomes 4
- check i=4: false
Now showing: init total=0. The update always waits until the body is done.
Arrays get a full lesson in Stage 3. For now, you need four facts:
- An array is a list of values in square brackets, like
[8, 10, 6]. - Each value has a position number, called its index. The first index is 0, not 1.
scores.lengthis how many values the array holds. Here it is 3.scores[i]reads the value at indexi.
So i < scores.length stops right after the last index, 2.
const scores = [8, 10, 6];
let total = 0;
for (let i = 0; i < scores.length; i++) {
total += scores[i];
}
console.log(total);Line 3 counts i from 0 to 2. Line 4 adds the score at index i to total. Line 6 prints 24, which is 8 + 10 + 6.
To count down, turn the header round. Start high, keep going while n is at least 1, and use n-- to take 1 away after each lap. This loop prints 3, 2, then 1. You might use it for a countdown, or to handle the newest item first.
for (let n = 3; n >= 1; n--) {
console.log(n);
}A string can be read by index too, because each character has a position. word.length is how many characters the string has, and word[i] reads the character at position i. This loop prints t, e, and a.
const word = "tea";
for (let i = 0; i < word.length; i++) {
console.log(word[i]);
}Use an index like this when you need the position, for example to change one item in a list. If you only need each value, the for...of loop is shorter. You'll learn it in the next lesson, for...of & for...in.
break and continue
STEP THROUGHTwo words let you change what a loop does, from inside its body:
breakleaves the loop right away. No more laps run.continueends the current lap early. The loop goes on to its update and its check, and the next lap runs as normal.
continue ends only the lap it is in; the next lap still runs. break ends the whole loop, and the code after the loop runs next.In the replay, the loop counts n from 1 to 20. Odd numbers hit continue, so they skip the rest of their lap. The first even number that 7 divides evenly is 14. The loop prints 14, and then break ends the search.
Watch continue end the lap for each odd number, and break end the whole loop at the first even number that 7 divides evenly.
script
if (n % 2 === 1) continue; if (n % 7 === 0) { console.log(n); break; }}Each row of this table is one of the two words: what it does, and when people use it.
| Word | What does it do? | When do you use it? |
|---|---|---|
break | It leaves the loop now. | You found what you were looking for, so you stop. |
continue | It skips the rest of this lap. | You want to ignore some values and keep going. |
Labels for outer loops
RARE TOOLA loop inside another loop is called a nested loop. Inside a nested loop, break and continue work on the nearest loop around them: the inner one.
A label is a name with a colon, written in front of a loop, like outer:. With a label, break outer leaves the outer loop instead of only the inner one. Labels are allowed in JavaScript, but you will rarely see them. Often it is clearer to put the nested loops in a function and use return to stop.
You will rarely need a label, but here is one. Step through a search over rows and columns that leaves both loops when it finds a match.
script
for (let col = 1; col <= 3; col++) { if (row * col === 6) { console.log(row + "," + col); break outer; } }}continue outer also works. It skips the rest of the inner loop and goes straight to the next lap of the outer loop. In this code, when col is 2, line 3 jumps to the next row. So only column 1 of each row prints. Use labels only when you must; if one makes the code hard to follow, a small function is easier to read.
outer: for (let row = 1; row <= 3; row++) {
for (let col = 1; col <= 3; col++) {
if (col === 2) continue outer;
console.log(row + "," + col);
}
}Avoiding infinite loops
SAFETYAn infinite loop is a loop whose condition never becomes false, so it never stops. While it runs, JavaScript can't do anything else. In a browser, the page freezes: it can't redraw or answer clicks. That is because a page's JavaScript runs one thing at a time, on the main thread. You met the main thread in the Engines, runtimes & hosts lesson.
Never test an infinite loop by really running one. Add a guard instead: a second way out, such as a limit on the number of laps. The safety lab below shows two risky loops. Each one has a guard, so it stops.
When a loop looks stuck, ask these three questions, in this order:
- Can the condition be
trueat the start? - Does the body change a value that the condition reads?
- Is there a limit, or a
break, that ends the loop?
If the answer to question 2 is no, the loop never gets closer to stopping.
i < 3 reads i. If the body never changes i, the answer never changes either. With i++, every lap moves i one step closer to 3.let i = 0;let steps = 0;while (i < 3) { steps++; // Bug: forgot i++ here. if (steps === 1000) { console.log("stopped by safety guard"); break; }}Without a guard, this loop would never stop, because i stays 0. The guard stopped it after 1000 laps and printed "stopped by safety guard".
One lap too many: <= or <
This is called an off-by-one bug: the loop runs one lap too many, or one too few. If the indexes are 0, 1, and 2, then i < 3 is right. i <= 3 runs one extra lap, with i equal to 3, and may read past the end of the list.
Changing the wrong variable
while (i < 3) needs a body that changes i. If the body changes j instead, or changes nothing, i < 3 may stay true forever.
Waiting for an exact decimal
Adding decimals, like x += 0.1, gives numbers that are very close but not always exact. So x may never equal 1 exactly. Use x >= 1, or count whole-number laps, instead of waiting for x === 1.
Where you’ll use loops
You'll use a loop whenever a program handles many things of the same kind: tries, rows, scores, letters, items in a cart, or search results. Here are common jobs for a loop:
- Ask again, or check again, until the answer is usable.
- Count from one number to another, for a score, a timer, or page numbers.
- Look through values until you find a match, then
break. - Skip values you don't need with
continue. - Go through a short list by index when you need each position.
- Count from 1 to 10
- Ask again until the user types a usable answer
- Read one line, then check if more lines are left
- Go through every item in a list by index, from
0toitems.length - 1 - Stir until the sauce is thick, however many stirs that takes
- Taste once, then keep tasting only while it still needs salt
Put each task under the loop that fits it best. Each explanation tells you the clue to look for.
One warning for later: the older keyword var behaves differently from let in loops. The difference shows up when a function remembers the loop variable. The Closures lesson explains it. For now, use let in your loop headers.
Common misconceptions
“A for loop always runs a fixed number of times.”
Often it does. But break can stop it early. And if the body changes the counter, or the condition reads data that changes, the loop can run fewer or more laps.
“do...while is just another way to write while.”
The check is in a different place. A while loop may run zero times. A do...while loop always runs at least once.
“continue leaves the loop.”
continue ends only the current lap. break leaves the whole loop.
“A frozen page means JavaScript crashed.”
It may just be busy running a long loop. The browser can't redraw the page or answer clicks until the loop ends.
“Labels are like goto.”
Some older languages have a goto command that jumps to any line. A JavaScript label can't jump anywhere. It only gives break and continue a loop to aim at.
Practice: write and fix loops
6 EXERCISESGuess the answer first. Then run the code in your console or editor to check. Each task practices one small part of loops.
How many times does the loop body run before the program prints?
let count = 0;
while (count < 3) {
count++;
}
console.log(count);The body runs while count < 3. It runs when count is 0, 1, and 2, so 3 times. After the third lap, count is 3, and the next check gives false.
Write a for loop that adds up all the numbers from 1 to 100, then prints the sum.
let total = 0;
// write the loop here
console.log(total);let total = 0;
for (let n = 1; n <= 100; n++) {
total += n;
}
console.log(total);The counter takes every whole number from 1 to 100. total += n adds each one to the running total. The final sum is 5050.
Print the numbers 1 to 15, with three changes:
- If 3 divides the number evenly, print
Fizzinstead. - If 5 divides the number evenly, print
Buzzinstead. - If both 3 and 5 divide it evenly, print
FizzBuzzinstead.
for (let n = 1; n <= 15; n++) {
// print Fizz, Buzz, FizzBuzz, or n
}for (let n = 1; n <= 15; n++) {
if (n % 15 === 0) {
console.log("FizzBuzz");
} else if (n % 3 === 0) {
console.log("Fizz");
} else if (n % 5 === 0) {
console.log("Buzz");
} else {
console.log(n);
}
}If both 3 and 5 divide a number evenly, 15 does too. So the 15 check must come first, or the 3 check would catch the number. The last line, for 15, is FizzBuzz.
This loop should print only 0, 1, and 2, but it prints 3 too. Which comparison operator fixes it?
for (let i = 0; i <= 3; i++) {
console.log(i);
}for (let i = 0; i < 3; i++) {
console.log(i);
}0, 1, and 2 are three values. The condition i < 3 stops the loop before it prints 3.
Add a guard so this loop can't run forever if ready never becomes true.
let tries = 0;
let ready = false;
while (!ready) {
tries++;
// add a guard that stops after 5 tries
}let tries = 0;
let ready = false;
while (!ready) {
tries++;
if (tries === 5) {
console.log("stopped by guard");
break;
}
}ready never changes, but tries goes up every lap. After five laps, the guard prints stopped by guard, and break ends the loop.
Look through the array from left to right. Stop as soon as you find a number below 0.
const numbers = [4, 9, 0, -3, 8];
let firstNegative = null;
// search here
console.log(firstNegative);const numbers = [4, 9, 0, -3, 8];
let firstNegative = null;
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] < 0) {
firstNegative = numbers[i];
break;
}
}
console.log(firstNegative);The loop checks each value in order. At -3, it saves the value and breaks, so it never looks at 8.
Quiz: check your understanding
8 QUESTIONSRead every explanation, even for the choices you didn't pick.
Question 1 of 8When does a
whileloop check its condition?Choose an answer to see the explanation.
Question 2 of 8What does the while loop print?
Read the code, then predictlet n = 3; while (n < 3) { n++; } console.log(n);Choose an answer to see the explanation.
Question 3 of 8What does the for loop total print?
Read the code, then predictlet total = 0; for (let i = 0; i < 3; i++) { total += i; } console.log(total);Choose an answer to see the explanation.
Question 4 of 8Which part of
for (let i = 0; i < 5; i++)runs only once?Choose an answer to see the explanation.
Question 5 of 8What do the continue examples print?
Read the code, then predictfor (let n = 1; n <= 5; n++) { if (n % 2 === 1) continue; console.log(n); }Choose an answer to see the explanation.
Question 6 of 8What does
breakdo inside a loop?Choose an answer to see the explanation.
Question 7 of 8Why can
while (x !== 1) { x += 0.1; }be dangerous?Choose an answer to see the explanation.
Question 8 of 8When is a label worth using?
Choose an answer to see the explanation.
Key takeaways
whilechecks before the body, so it can run zero times.do...whilechecks after the body, so it runs at least once.- A
forheader holds three parts: the initializer runs once, the condition runs before each lap, and the update runs after each lap. breakleaves the loop.continueends only the current lap.- A label, like
outer:, letsbreakorcontinuework on an outer loop. You will rarely need one. - Every loop must change something each lap, so it gets closer to stopping. Add a guard when you can't be sure it will stop.
Remember the one-liner.
A loop runs a block of code again and again while its condition is true. A safe loop changes something each lap, so the condition becomes false at some point.
Up next: for...of & for...in.