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.

Tech Talk News Editorial6 min read
ShareXLinkedInRedditEmail
Loops in Java: for, forEach, while, and do-while

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

A loop is a piece of code that runs multiple times. The four Java loop constructs differ in how the iteration count is determined and where the termination condition is checked.

The Traditional for Loop

for loopjava
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)

Enhanced forjava
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)

Stream forEachjava
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

whilejava
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

do-whilejava
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:

Stream APIjava
// 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 < n vs i <= 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.

ShareXLinkedInRedditEmail