JAVA / LOOPS AND ARRAYS
while loops and loop termination reasoning
Write while loops whose stopping condition you can justify, naming the value that shrinks on every pass so you can tell a finite loop from a hang.
What you will learn
- Read the condition as 'keep going while true' and know it is tested before every pass
- Prove a loop ends by naming a value that strictly shrinks toward a bound the test uses
- Prefer > or <= over != so an update that overshoots the target still ends the loop
- Cap loops driven by data or unproven convergence with an explicit step counter
Understanding while loops and loop termination reasoning
A while loop evaluates its condition before every pass, including the first, so the loop can run zero times and every variable the condition reads must already hold a sensible value when control reaches the loop. Because a while header has no update slot, the only thing that can turn the condition false is a statement in the body; nothing else will do it for you. That is what makes while the natural shape when you cannot say up front how many passes you need: you describe the situation in which work remains, and the loop keeps going while that description stays true.
Termination is a claim you should be able to defend. The usual argument is to name a quantity, often called the loop's measure or variant, that changes strictly in one direction on every pass and cannot cross a bound the condition tests. In while (n > 1) { n = n / 2; } the measure is n itself: integer division of a value greater than 1 always yields something smaller, and never yields less than 1, so n must arrive at 1 and the condition must fail. If you cannot name such a quantity for a loop you have written, you have not written a loop that ends, only one that happened to end for the input you tried.
This is also why != makes a weak stopping test. A condition like n != 0 fails at exactly one value, so any update that can step over that value leaves the condition true forever: adding 3 at a time to reach 10, subtracting 7 from a number that is not a multiple of 7, or accumulating doubles that never land exactly on 1.0. A range test such as n <= 0 fails for infinitely many values, so overshooting still stops the loop. When the condition depends on something you do not control, pair it with a counter and a hard maximum, so the worst case is a visibly wrong answer instead of a process you have to kill.
Keep the variables the condition uses in scope after the loop. Once the loop exits you know the whole condition is false, but with a compound condition you do not know which half failed until you look, and printing the final state is the cheapest way to confirm the loop stopped for the reason you intended.
public class Halving {
public static void main(String[] args) {
int n = 100;
int steps = 0;
while (n > 1) { // tested before every pass
n = n / 2; // strictly smaller, and never below 1
steps++;
System.out.println("step " + steps + ": n = " + n);
}
System.out.println("loop ended because n > 1 is false, n = " + n);
System.out.println("steps taken: " + steps);
}
}A while loop ends only if every pass moves some quantity the condition tests strictly toward failing it, so termination is something you argue from the code rather than something the syntax guarantees.
Worked examples
A stopping condition that never fires
Shows why an equality test against a double target is not a valid stopping condition.
public class FloatingStop {
public static void main(String[] args) {
double x = 0.0;
int added = 0;
while (x != 1.0 && added < 10) {
x += 0.1;
added++;
}
System.out.println("added = " + added);
System.out.println("x = " + x);
System.out.println("x == 1.0 is " + (x == 1.0));
}
}Example explained
Line 1x += 0.1 adds the double closest to one tenth, which is slightly off, and the running sum is rounded again on every pass.
Line 2After ten passes x is 0.9999999999999999, so x != 1.0 is still true and the loop has already sailed past its intended target.
Line 3Only added < 10 stops the run; delete that clause and the loop keeps adding, because == on doubles tests one exact bit pattern.
Line 4A range test such as x < 1.0 would have failed here, which is why comparisons beat equality when the update is not exact.
The condition is only tested at the top
Demonstrates that the rest of the body still runs after the condition has already become false.
public class TopCheck {
public static void main(String[] args) {
int fuel = 2;
while (fuel > 0) {
fuel--;
System.out.println("burned one unit, fuel = " + fuel);
System.out.println("body still running with fuel = " + fuel);
}
System.out.println("exited with fuel = " + fuel);
}
}Example explained
Line 1fuel-- runs first, so the value printed is exactly the value the next condition test will see.
Line 2On the second pass fuel reaches 0 halfway through the body, yet the remaining statement still executes; the condition is not rechecked mid-body.
Line 3The third test finds 0 > 0 false, so the body is skipped and control continues after the loop.
Line 4fuel was declared before the loop, so its final value is still readable and confirms which comparison ended the loop.
Bounding a loop you cannot prove ends
Adds an artificial measure to a loop whose real condition has no shrinking quantity behind it.
public class BoundedLoop {
public static void main(String[] args) {
long n = 7;
int steps = 0;
int cap = 100;
while (n != 1 && steps < cap) {
n = (n % 2 == 0) ? n / 2 : 3 * n + 1;
steps++;
}
if (n == 1) {
System.out.println("reached 1 after " + steps + " steps");
} else {
System.out.println("hit the cap of " + cap + " steps at n = " + n);
}
}
}Example explained
Line 1n goes 7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1, so it grows on odd steps and there is no quantity you can point to that always shrinks.
Line 2steps < cap supplies a measure by hand: cap - steps drops by one every pass, so the loop cannot run more than 100 times no matter what n does.
Line 3Testing n == 1 after the loop is what separates success from giving up, since exiting alone does not say which clause of the condition failed.
Line 4n is a long because 3 * n + 1 can climb well above the starting value before it comes back down.
Important notes
while (true) is legal, but with no reachable break or return inside it Java reports any statement after the loop as an unreachable statement error; a loop whose condition is a variable that never changes compiles cleanly and just hangs.
A hanging loop is not a compiler problem to fix but a reasoning gap: if you cannot say which value shrinks, add the step cap first and investigate afterwards.
Common mistakes
Testing equality against a target the update can jump over: int i = 0; while (i != 10) { i += 3; } visits 0, 3, 6, 9, 12 and never sees 10, so the program hangs until you kill it.
Forgetting the update inside the body because a while header has no update slot: the condition reads the same value forever and the same line prints without end.
Writing while (count < 5); before the block: the semicolon makes the empty statement the body, so the loop spins with count unchanged and the block below never gets its turn.
Try it yourself
Change, predict, then run
Write a loop that repeatedly subtracts 7 from int balance = 100 while balance >= 7, counting passes, and print the count and the leftover balance. Then change the condition to balance != 0 with a guard of at most 50 passes, print balance each pass, and explain in a comment why the equality test never fires.
Open the Java workspaceCheck your understanding
Given int n = start; for an arbitrary int start, which loop is guaranteed to terminate for every possible value of start?
- while (n != 1) { n = n / 2; }
- while (n > 1) { n = n / 2; }
- while (n < 100) { n = n * 2; }
- while (n != 0) { n = n - 2; }
Show answer
With n > 1, either the body never runs (start is 1 or less) or n is greater than 1, and then integer division gives a strictly smaller value that is still at least 1, so n must reach 1 and the test must fail. Option 0 looks like the same loop but tests one exact value: start of 0 gives 0 / 2 = 0 forever while n != 1 stays true. Option 2 sticks at 0 for start of 0, and option 3 keeps the parity of start, so an odd start steps over 0 forever.