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 readUpdated Jul 14, 2026
ShareXLinkedInRedditEmail
Loops in Java: for, forEach, while, and do-while

Key takeaways

  • Java has four loop constructs: the traditional for loop, the enhanced for loop, the while loop, and the do-while loop. Use the enhanced for to iterate a collection, and the traditional for when you need an index.
  • The enhanced for loop (for (String s : list)) works on any array and on any class implementing Iterable, but you cannot modify the collection during iteration without triggering a ConcurrentModificationException.
  • Iterable.forEach() is a method that takes a Consumer, not a loop construct. It arrived with Java 8 lambdas, and you cannot break out of it or throw checked exceptions from inside it.
  • The do-while loop checks its condition after running the body, so it always executes at least once. It is genuinely rare in modern Java, and most uses can be rewritten as a while loop.
  • The three loop bugs that account for most real defects are off-by-one errors (i < n vs i <= n), modifying a collection while iterating it, and forgetting to update the condition variable in a while loop.

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 (Java 16+ has .toList() instead of .collect(...))
List<Integer> doubled = numbers.stream()
    .filter(n -> n > 0)
    .map(n -> n * 2)
    .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

Once the language has streams, loop choice is mostly a readability decision. The traditional for is rarely the right answer anymore outside of code that genuinely needs indices, and the enhanced for plus the Stream API cover most modern Java collection processing. Here's the part beginner courses get backwards: they spend a week on which loop to pick and about ten minutes on what goes inside it. Almost every loop bug I've ever had to debug was in the body, not the construct. Pick the obvious loop, then spend your attention on the four lines between the braces.

Frequently asked questions

What is the difference between a while loop and a do-while loop in Java?
A while loop checks its condition before running the body, so it can run zero times. A do-while loop runs the body first and checks the condition afterwards, so it always runs at least once. Use do-while only when that guaranteed first pass is what you actually want, such as prompting for input until the user enters a sentinel value.
Which Java loop is fastest?
For practical purposes they are the same. The JIT compiler flattens the differences, and the enhanced for loop compiles down to an iterator or an indexed loop depending on the type. Choose on readability, not on micro-benchmarks. If a loop is genuinely hot, the win comes from the algorithm or from avoiding boxing, not from the loop keyword.
Should I use streams instead of loops in Java?
Use streams for multi-stage transformations: filter, map, then collect. Use a plain loop when you need to break early, mutate external state, or throw a checked exception, because streams handle all three badly. Streams are not faster than loops by default, they are clearer for pipelines and messier for everything else.
Why does removing an element inside a for-each loop throw an exception?
The enhanced for loop uses an Iterator under the hood, and most collections track a modification count. Changing the collection through any other path invalidates the iterator, which throws ConcurrentModificationException on the next step. Use Iterator.remove(), removeIf(), or collect the changes and apply them after the loop.

Written by

Tech Talk News Editorial

Computer engineering background. Writes about software, AI, markets, and real estate, and the places where the three meet.

More about the author
ShareXLinkedInRedditEmail