A while loop scatters its three parts across three different places: the initialization sits above the loop, the condition sits in the header, and the update hides somewhere in the body. That is fine until the body grows and you can no longer see them at a glance. A for loop gathers all three onto one line, which makes a counting loop readable in a single glance and makes a forgotten update almost impossible.
for (int i = 0; i < 5; i++) {
System.out.println(i);
}| Piece | Name | When it runs |
|---|---|---|
int i = 0 | Initialization | Once only, before anything else |
i < 5 | Condition | Before every pass, including the first |
System.out.println(i) | Body | Only when the condition was true |
i++ | Update | After the body, before the condition is re-tested |
- You know the number of passes before you start
- You are stepping through positions 0 to n-1
- You are counting up, down, or by a fixed step
- Repeat exactly 12 times
- Print every item in an array
- The number of passes is unknown in advance
- The loop ends on an event, not a count
- The control variable is not a simple counter
- Keep reading until the user types a sentinel
- Keep halving until the value drops below 1
Why programmers count from 0
Starting at 0 looks unnatural for about a week and then becomes the only thing that makes sense. Java numbers array positions from 0, so an array of 5 items has positions 0, 1, 2, 3, 4 — and for (int i = 0; i < 5; i++) hits exactly those and nothing else. The bound and the length are then the same number, so i < marks.length is always right and you never have to remember to subtract one. Read i < n as "how many" and i <= n as "how far": starting at 0, the condition i < n runs the body n times, while starting at 1, the condition i <= n also runs it n times. Pick one pairing and stay with it, because mixing them is how off-by-one errors are born.
// Counting down: start high, end low, and decrement
for (int i = 10; i >= 1; i--) {
System.out.print(i + " ");
}
System.out.println("Liftoff!");
// Output: 10 9 8 7 6 5 4 3 2 1 Liftoff!
// Counting by a step other than 1
for (int i = 0; i <= 20; i += 5) {
System.out.print(i + " ");
}
// Output: 0 5 10 15 20Off-by-one errors
An off-by-one error is a loop that runs one time too many or one time too few, and it is the most common bug in all of programming. It comes down to one character: < instead of <=, or a starting value of 0 instead of 1. The code compiles, looks correct, and produces an answer that is nearly right — which is worse than an obvious crash, because you may never notice.
| Header | Values taken by i | Number of passes |
|---|---|---|
for (int i = 0; i < 5; i++) | 0, 1, 2, 3, 4 | 5 |
for (int i = 0; i <= 5; i++) | 0, 1, 2, 3, 4, 5 | 6 |
for (int i = 1; i <= 5; i++) | 1, 2, 3, 4, 5 | 5 |
for (int i = 1; i < 5; i++) | 1, 2, 3, 4 | 4 |
When you only want each value in a collection and do not care about its position, Java offers a shorter form, read as "for each mark in marks". It cannot go out of bounds, because you never write an index, and it cannot run the wrong number of times. The trade-off is that you have no index to work with, so you cannot say "compare this item to the one before it" or write a new value back into the array.
int[] marks = {78, 85, 92, 64};
// Enhanced for: one pass per element, no index in sight
for (int mark : marks) {
System.out.println(mark);
}
// The indexed form, which you need when the position matters
for (int i = 0; i < marks.length; i++) {
System.out.println("Mark " + (i + 1) + " is " + marks[i]);
}