JAVA / LOOPS AND ARRAYS
do while and when the first run is guaranteed
Use do-while when a loop's body must run before there is anything to test, and recognise the inputs where that guaranteed first pass is a bug.
What you will learn
- Write a do-while whose body updates the value its own condition tests
- Choose do-while when the first pass must happen before a test is possible
- Spot the empty or zero input where the guaranteed pass breaks the loop
- Declare variables the condition reads above the do block, not inside it
Understanding do while and when the first run is guaranteed
A do-while statement runs its body first and evaluates the condition afterwards, which is why the body always executes at least once even when the condition is false from the very start. The written form follows that order: the body comes first and the test is last, closed by a semicolon that terminates the whole statement. The condition sits outside the body's block, so it is evaluated once per completed pass rather than before each pass.
The reason to reach for it is that some loops cannot ask their question until the body has produced an answer. Extracting the digits of a number, retrying a call until it succeeds, or reading one value before deciding whether to read more all have that shape. Written with while, those force you either to duplicate the first pass above the loop or to invent a fake starting value that makes the condition true, and both are places for bugs to settle. There is a related convenience: because the body always runs, a variable the body assigns can be declared with no initialiser and still be read in the condition, since the compiler knows the assignment has already happened.
The guarantee cuts both ways. A do-while over data that might be empty still performs one pass, so indexing data[0] throws on a zero-length array instead of being skipped, and a counter that should stop at zero drops to minus one. Before choosing the loop, name the degenerate input for it (zero, empty, already valid, succeeds on the first try) and decide whether exactly one pass is correct there. If zero iterations is a legal outcome, the loop belongs in a while and no rearranging of the body will change that.
public class GuaranteedFirstRun {
public static void main(String[] args) {
int stock = 0;
int whileRuns = 0;
while (stock > 0) {
whileRuns++;
stock--;
}
int doRuns = 0;
do {
doRuns++;
stock--;
} while (stock > 0);
System.out.println("while body ran " + whileRuns + " time(s)");
System.out.println("do body ran " + doRuns + " time(s)");
System.out.println("stock left: " + stock);
}
}do-while tests after the body, so it is the loop for work that must happen once before there is anything worth testing.
Worked examples
Digits of a number, including zero
A case where the post-test is the correct choice, because zero still has one digit to emit.
public class Digits {
public static void main(String[] args) {
System.out.println(digits(0));
System.out.println(digits(407));
}
static String digits(int n) {
StringBuilder sb = new StringBuilder();
do {
sb.append(n % 10);
n /= 10;
} while (n > 0);
return sb.reverse().toString();
}
}Example explained
Line 1sb.append(n % 10) happens before any test, so n = 0 still contributes the digit 0.
Line 2The same body under while (n > 0) would leave sb empty for 0 and return an empty string.
Line 3n /= 10 is what eventually falsifies the condition; integer division reaches 0 for any non-negative n.
Line 4Digits come out least significant first, so reverse() restores 407.
Retry until success, with the result declared outside
Shows why the condition cannot see names declared inside the body, and how the guaranteed pass removes the need for a dummy initial value.
public class RetryLoop {
public static void main(String[] args) {
int[] responses = {503, 503, 200};
int attempt = 0;
int status;
do {
status = responses[attempt];
System.out.println("attempt " + (attempt + 1) + " -> " + status);
attempt++;
} while (status != 200 && attempt < responses.length);
System.out.println("finished with status " + status + " after " + attempt + " attempt(s)");
}
}Example explained
Line 1status is declared above the do block because the condition is outside that block and cannot see names declared inside it.
Line 2It needs no initialiser: the body assigns it on every pass, so it counts as assigned by the time the condition is evaluated.
Line 3The first 503 is not a special case handled before the loop; it is simply iteration one.
Line 4The attempt < responses.length half of the condition is what stops the loop when 200 never arrives.
Important notes
continue inside a do-while jumps forward to the condition rather than back to the top of the body, so every continue causes the test to be evaluated.
do-while is uncommon in Java: when a for or while expresses the same loop without a sentinel value or a duplicated first pass, prefer it, since readers look for the exit test at the top.
Common mistakes
Converting a while loop over an array into a do-while: with a zero-length array the body runs before the bounds test, so data[0] throws ArrayIndexOutOfBoundsException.
Declaring the variable the condition tests inside the do block, which fails to compile with 'cannot find symbol' because the condition is not part of that block.
Assuming the test comes first, so a loop whose starting value is already acceptable still performs one pass; that is exactly why the counter in the main example ends at -1 instead of 0.
Try it yourself
Change, predict, then run
Write a method countDigits(int n) for non-negative n using a do-while, and print the result for 0, 7 and 1000. It should print 1, 1 and 4, which is what a while-based version would get wrong for 0.
Open the Java workspaceCheck your understanding
A working method sums an int array with while (i < data.length) { sum += data[i]; i++; }. Someone rewrites it as do { sum += data[i]; i++; } while (i < data.length); What is the effect?
- Identical behaviour for every possible input
- Identical sums for non-empty arrays, but ArrayIndexOutOfBoundsException when the array is empty
- Every non-empty array now sums to a value too large by data[0]
- It no longer compiles, because sum is read after the body instead of before it
Show answer
For any array with at least one element the guaranteed pass is just iteration one, so the sums match and the bug survives casual testing; with length 0 the body runs before the bounds test and reads data[0] from an empty array. Option 3 is tempting because 'runs at least once' sounds like an extra iteration, but no element is visited twice: the first pass is the normal first iteration, not an addition to it.