Loops in Java: for, forEach, while, and do-while
Java has four ways to write a loop, and most code only needs two of them. Here's the difference between each, when to use which, and the few places choosing wrong actually matters.
Java has four loop constructs: the traditional for loop, the enhanced for loop (forEach), the while loop, and the do-while loop. They overlap heavily in capability. Most code uses two: the enhanced for (when iterating a collection) and the traditional for (when you need an index). The other two have specific use cases that come up rarely. Knowing which is which prevents the small set of bugs that loop choice introduces.
The way I think about loops in Java is that they're the most over-explained topic in beginner programming and the most under-considered in production code. Every CS101 course spends a week on loops. Most professional Java code uses streams, lambdas, or the enhanced for and barely thinks about the loop construct. The minute you're writing index arithmetic in a traditional for, you should ask whether there's a better way.
Plain English
The Traditional for Loop
for (int i = 0; i < 10; i++) {
System.out.println(i);
}Three parts in the parentheses: initialization (int i = 0), condition (i < 10), and update (i++). Run on every iteration in that order: initialize once, check condition, run body, update, check again, until the condition is false.
Use it when you need an index (the position 0, 1, 2, ...) or when you're iterating non-collection things like incrementing across a range. For iterating a collection, the enhanced for is almost always cleaner.
The Enhanced for (forEach)
int[] numbers = {1, 2, 3, 4, 5};
for (int n : numbers) {
System.out.println(n);
}
List<String> names = List.of("Alex", "Jordan");
for (String name : names) {
System.out.println(name);
}Reads as “for each int n in numbers, do this.” Cleaner than the traditional for when you don't need the index. Works on any array or any class implementing the Iterable interface (which is almost every collection).
The trade-off: you can't modify the collection during iteration without throwing ConcurrentModificationException, you can't access by index, and you can't easily skip backwards. For 90% of iteration use cases, none of those matter. Use the enhanced for.
The forEach Method (Different Thing)
List<String> names = List.of("Alex", "Jordan");
names.forEach(name -> System.out.println(name));
// Or method reference
names.forEach(System.out::println);This is technically not a loop construct; it's a method on Iterable that takes a Consumer. It became common after Java 8's lambdas. Functionally similar to the enhanced for, slightly different semantics (you can't break out of it, you can't throw checked exceptions cleanly).
For collection iteration where you'd use the enhanced for and don't need to break early, this works equivalently. For more complex iteration, stick with the enhanced for.
The while Loop
int x = 0;
while (x < 10) {
System.out.println(x);
x++;
}
// Read until end of input
String line;
while ((line = reader.readLine()) != null) {
process(line);
}Just a condition. Runs as long as the condition is true. Use it when you don't know in advance how many iterations you need. The classic case: reading from a stream until it's done.
The trap is forgetting to update the condition variable inside the loop, which produces an infinite loop. The traditional for puts the update next to the initialization, which makes it visible. The while loop separates them, which is why bugs happen.
The do-while Loop
int input;
do {
input = scanner.nextInt();
System.out.println("You entered: " + input);
} while (input != 0);Runs the body first, then checks the condition. Guarantees at least one iteration. The use case is “do this thing, and keep doing it as long as some condition holds.” Reading user input until they enter a sentinel value is the textbook example.
Genuinely rare in modern Java code. Most cases where you'd reach for do-while can be rewritten with a while loop using a flag, and many style guides discourage do-while because the condition is hidden at the bottom and easy to miss when scanning code.
When to Use Which
Practical rules:
- Iterating a collection where you don't need the index: enhanced for.
- Iterating a collection where you do need the index: traditional for.
- Counting through a numeric range (0 to N): traditional for.
- Reading until a stream ends or a condition is met: while.
- Need to run the body at least once before checking: do-while (rare).
- Functional-style transformation: Stream API with map/filter/collect.
The Modern Alternative: Streams
Java 8 added the Stream API, which replaces many traditional loops:
// Old loop
List<Integer> doubled = new ArrayList<>();
for (int n : numbers) {
if (n > 0) {
doubled.add(n * 2);
}
}
// Stream version
List<Integer> doubled = numbers.stream()
.filter(n -> n > 0)
.map(n -> n * 2)
.collect(Collectors.toList());Streams are not always cleaner. For simple transformations they're a slight win. For complex multi-stage processing they're much cleaner. For heavy mutation or break-out logic, traditional loops are usually better.
The Common Bugs
Three patterns to watch:
- Off-by-one errors.
i < nvsi <= n. Almost always the cause when a loop runs one too many or one too few times. - Modifying a collection during iteration. Adding or removing from a list while iterating it throws ConcurrentModificationException. Use an Iterator with explicit remove(), or collect changes and apply after the loop.
- Infinite loops. Forgetting to update the condition variable inside a while loop. The traditional for makes this less likely.
Takeaway
Use the enhanced for for collection iteration. Use the traditional for when you need indices or counters. Use while for unbounded iteration with a condition. Use do-while almost never. Consider the Stream API for transformations. The same algorithm can be written multiple ways in Java; the cleanest one is usually the answer.
The Take
Loop choice in Java is mostly a style and readability decision after the language adds streams. The traditional for is rarely the right answer in 2025 outside of code that genuinely needs indices. The enhanced for and the Stream API cover most modern Java collection processing. Spend more energy thinking about whether the loop body is correct than which loop construct you used. Most loop bugs are in the body, not the construct.
Written by
Tech Talk News Editorial
Tech Talk News covers engineering, AI, and tech investing for people who build and invest in technology.