Control Flow
Loops
A loop repeats a block while a boolean condition holds. while tests first, so it may run zero times, and do-while tests after, so it runs at least once. The classic for packs init, test and update into one line, and the enhanced for (for-each) walks elements without an index. Covers off-by-one boundaries, loop-variable scope and the for-each "copy" surprise, all with verified output.
Suggest an editLoops — Repeating Work
Computers are great at doing the same thing over and over, and a loop is how you ask for that: repeat a block of code while a boolean condition stays true. Java gives you four loop shapes. They differ only in when they check the condition and what drives the repetition:
whilechecks before each pass;do-whilechecks after each pass;- the classic
forbundles a counter's setup, test and step onto one line; - the enhanced
forwalks a collection of values directly.
Master the counter and the boundary, and every loop you'll meet is a variation on these.
💡 The core idea.
- A loop repeats a block while a
booleancondition stays true. - Java's four loop shapes differ only in when the condition is checked and what drives the repetition.
- Master the counter and the boundary and every loop is a variation on these.
This rests on boolean conditions: the same <, == and && that drive an if drive a loop. Every output below was produced by compiling and running the code.
You'll be able to: predict how many times a while, do-while or for loop runs, from its start value and its boundary; rewrite a counting while as a for, and name where the for variable stops existing; find why a loop never ends, or runs its body when it should not; walk every element with the enhanced for, and explain why assigning to its variable changes nothing; explain why a double counter from 0.0 to 1.0 in steps of 0.1 runs 11 times, and count with an int instead.
📘 How to read the Intuition boxes. Each one is built in three moves:
- The mechanism — what the compiler and the JVM do.
- A concrete bite — a specific, runnable failure (often a real compiler error), shown so the trap is visible.
- The earned rule — the decision heuristic, now justified rather than asserted, plus its cost.
Table of contents
while: test before each passdo-while: test after each pass- The classic
for - The enhanced
for(for-each) - Mental-model summary
- Gotcha checklist
- Check yourself
- Sources
1. while: test before each pass
A while loop checks its condition, and if it's true, runs the body. Then it checks again, and again, until the condition becomes false [1]. Something inside the body has to move toward that end, or the loop never stops.
Output:
0 1 2 3 4 Analysis. i starts at 0. The loop printed 0, incremented to 1, checked 1 < 5 (true), and so on, printing 0 1 2 3 4. When i reached 5, the test 5 < 5 was false, so the loop stopped before printing 5. The i++ is what moved the counter toward the exit.
Intuition.
Mechanism. while (cond) evaluates cond before each pass, and the body runs only while it's true. If the body never changes what cond depends on, the condition stays true forever. That's an infinite loop.
Non-example: the missing update. Leave out the i++, and nothing ever makes i < 3 false:
Output (prints one line, then runs until it is stopped; the sandbox stops it at its time limit):
before the loopThere's no error message. The program just keeps running, and after the loop never prints. In a terminal, you stop a runaway program with Ctrl+C.
Concrete bite. The boundary is where loops go wrong. Change < to <=, and the loop runs one extra time:
Output:
0 1 2 3 4 5 The same loop with <= prints 0 through 5: six numbers, not five. "Count five things starting at zero" means i < 5 (indices 0..4). i <= 5 is the off-by-one that does one too many.
💡 Earned rule. Write the condition so the body advances toward making it false, and pin down the boundary deliberately: i < n counts n times from 0.
A loose boundary costs you an off-by-one: one pass too many or too few. Forgetting the advance entirely costs you an infinite loop. You get no wrong output and no error, just a program that never returns.
2. do-while: test after each pass
A do-while runs the body first, then checks the condition [2]. So the body always runs at least once, even when the condition is false from the start.
Output:
while done
do body
do doneAnalysis. Both loops start with their variable at 10 and a condition, < 5, that's already false.
- The
whilechecked first, found10 < 5false, and ran its body zero times:while bodynever printed. - The
do-whileran its body once before checking, sodo bodyprinted. Then11 < 5was false and it stopped.
Notice the ; after while (m < 5). A do-while ends with one, and javac reports ';' expected if you leave it out.
Intuition.
Mechanism. The only difference is the order of test and body. while is test-then-body (zero or more passes), and do-while is body-then-test (one or more passes).
Concrete bite. A variable declared inside the do body ends at the body's closing brace. The condition sits after that brace, so it can't see the variable:
Compiler error:
Main.java:6: error: cannot find symbol
} while (tries < 3);
^
symbol: variable tries
location: class Main
1 errorA variable lives from its declaration to the end of the block that holds it [5]. Declare tries before the do, and the condition can read it.
💡 Earned rule. Use do-while only when the body must run once before any test makes sense, such as prompting for input you then validate.
Reach for it out of habit, and you pay with a body that runs even when it shouldn't. while (or for) is the right default, because most loops should be able to run zero times.
3. The classic for
Counting loops have three moving parts: a starting value, a continue-condition, and a step. The for loop puts all three on one line: for (init; test; update) [3]. It's the same logic as the while in §1, gathered in one place.
Output:
1 2 3 4 5 Analysis. int i = 1 ran once at the start. Then the cycle begins: test i <= 5, run the body if it's true, do i++, and test again. This time the boundary is <= 5 because the count starts at 1, so it prints 1 2 3 4 5. The init, test and update sit side by side, so you can read the loop's intent at a glance.
Each part can hold more than one thing, separated by commas. Here two counters walk toward each other:
Output:
0 5
1 4
2 3After the third pass, i is 3 and j is 2, so i < j fails. Each part is optional, too. for (;;) has no test at all, and it runs until something inside it leaves the loop. Loop Control & Patterns shows how.
Intuition.
Mechanism. The init runs once. Then comes test, body, update, test, … until test is false. The loop variable declared in init lives only inside the loop: its scope is the for and its body, and nothing after [5].
Concrete bite. Use the loop variable after the loop, and the compiler can't find it:
Compiler error:
Main.java:6: error: cannot find symbol
System.out.println(i);
^
symbol: variable i
location: class Main
1 errori exists only within the for. After the closing brace it's gone, so referring to it is a compile error. If you need the final value afterward, declare the variable before the loop:
Output:
0
1
2
after: 3The last value is 3, not 2. The loop stops only when the test fails, and it fails at 3.
Non-example: a stray semicolon. Just as with if in Conditionals, a ; after the header becomes the whole body:
Output:
hiThe loop ran three times with an empty body. Then the block after it ran once, on its own. (A while (i < 3); is even worse: its empty body never changes i, so it never ends.)
Non-example: a double counter. A counter that steps by 0.1 inherits the rounding you saw in Numbers & Arithmetic:
Output:
11Ten steps of 0.1 should reach 1.0. But the sum lands a hair below it, at 0.9999999999999999, so x < 1.0 passes an eleventh time. Count with an int instead, and compute the double from it:
Output:
10💡 Earned rule. Use a for when you know the counting structure up front (a fixed range, a step), and let its declared variable stay scoped to the loop. Count with an int, never a double.
That tidy scoping costs you the counter, which vanishes at the loop's end. The payoff: you can't accidentally read a stale loop counter later, and two loops side by side can both use i without colliding.
4. The enhanced for (for-each)
When you want each element of a collection, use the enhanced for [4]. Read for (T x : items) as "for each x in items". It hands you the elements one at a time, with no counter and no index to manage.
Here we iterate over an array, a fixed list of values. Arrays get their full treatment in Arrays. For now, read int[] nums = {2, 4, 6} as "a list of numbers".
Output:
12Analysis. The loop ran once per element: n was 2, then 4, then 6, adding each to sum (0 → 2 → 6 → 12). There's no index and no boundary to get wrong. The loop visits every element exactly once and stops. It's the safest loop when "every element, in order" is all you need.
Non-example: a String isn't a list here. A String holds characters, but the enhanced for accepts only an array or an Iterable, the type behind Java's collections:
Compiler error:
Main.java:4: error: for-each not applicable to expression type
for (char c : word) {
^
required: array or java.lang.Iterable
found: String
1 errorword.toCharArray() gives the characters as an array, and the loop accepts that:
Output:
a-b-c-Intuition. Mechanism. Each pass copies the next element into the loop variable [4]. That variable is a copy of the element, not the slot it came from. So assigning to it changes the copy, never the array.
Concrete bite. Try to modify elements through the loop variable, and nothing happens:
Output:
6The first loop "multiplied each element by 10", yet the second loop sums to 6 (1 + 2 + 3), not 60. That proves the array was never touched. n = n * 10 updated a throwaway copy on each pass.
💡 Earned rule. Reach for the enhanced for whenever you need each element and nothing more. It removes the index, and the off-by-one bugs go with it.
The cost is exactly what it hides:
- you get no index, so you can't say "the 3rd element" or compare neighbours;
- you can't write back into the array through the loop variable.
When you need the position, or want to modify elements in place, use the classic for with nums[i].
5. Mental-model summary
| Principle | Consequence |
|---|---|
while tests before the body |
It can run zero times; the body must advance toward the exit, or it loops forever |
do-while tests after the body |
It always runs at least once; its condition cannot see variables declared in the body |
for bundles init/test/update; its variable is loop-scoped |
Tidy counting in one line; the counter doesn't exist after the loop |
The boundary (< vs <=) sets the count |
i < n from 0 runs n times; <= is the classic off-by-one |
A ; after a loop header is the whole body |
The block after it runs once, after the loop; while (…); can hang |
A double counter drifts |
Count with an int; compute the double from it |
The enhanced for gives each element as a copy |
No index, and assigning to the loop variable doesn't change the array; a String needs toCharArray() |
6. Gotcha checklist
| Symptom | Likely cause | Fix |
|---|---|---|
| The loop runs one time too many or too few | off-by-one at the boundary | i < n from 0 counts n times |
| The program hangs and prints nothing more | an infinite loop: the body never changes what the condition depends on | make the body advance the counter; stop the program with Ctrl+C |
| A loop body runs once, whatever the condition | a ; straight after for ( … ) |
delete the ; |
cannot find symbol using the counter after the loop |
a for-declared variable is scoped to the loop |
declare it before the loop |
cannot find symbol in a do-while condition |
the variable was declared inside the do body |
declare it before the do |
';' expected after while ( … ) |
a do-while is missing its closing ; |
} while (cond); |
| A body that should sometimes be skipped always runs once | you used do-while |
switch to while or for, so zero passes is possible |
A double loop runs one extra time |
the counter drifted below the limit | count with an int |
for-each not applicable to expression type |
a String (or a number) after the : |
loop over word.toCharArray() |
| Modifying elements in a for-each has no effect | the loop variable is a copy | the classic for with nums[i] |
✅ Check yourself
One check per objective. Answer before you open anything.
The 🧪 box below: the countdown, the two loops that start at 3, and the §1 loop as a for.
for (int i = 10; i > 0; i -= 2) prints five numbers: 10 8 6 4 2. At 0, the test 0 > 0 fails.
With the variable at 3 and the condition < 3, the while body runs 0 times, and the do-while body runs 1 time.
The §1 loop as a for:
for (int i = 0; i < 5; i++) { System.out.print(i + " "); }
It prints 0 1 2 3 4, the same line.
📚 Sources
- The Java Language Specification, Java SE 25, §14.12 "The
whileStatement" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-14.html#jls-14.12 - The Java Language Specification, Java SE 25, §14.13 "The
doStatement" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-14.html#jls-14.13 - The Java Language Specification, Java SE 25, §14.14.1 "The basic
forStatement" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-14.html#jls-14.14.1 - The Java Language Specification, Java SE 25, §14.14.2 "The enhanced
forstatement" (an array, or anIterable) — https://docs.oracle.com/javase/specs/jls/se25/html/jls-14.html#jls-14.14.2 - The Java Language Specification, Java SE 25, §6.3 "Scope of a Declaration" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-6.html#jls-6.3
🧪 Predict, then check.
- Predict the exact output of
for (int i = 10; i > 0; i -= 2) System.out.print(i + " ");: how many numbers, and which? - Predict how many times each loop body runs when the variable starts at
3and the condition is< 3: awhile, then ado-while. - Rewrite the §1
while(printing0 1 2 3 4) as aforloop, and confirm it prints the same line.
Your Turn
Before you move on, check your understanding with the coach — explain the idea, apply it, weigh the trade-offs, then defend your reasoning.