Java for Loop: Classic, Enhanced, and Stream Alternatives

Java has two for loops β€” the classic indexed loop and the enhanced for-each loop β€” plus the functional stream alternative. Knowing which to pick keeps code concise and correct.

Classic for loop

for (int i = 0; i < 10; i++) {
    System.out.println(i);
}

Three semicolon-separated parts, each optional:

  1. Initialization: runs once before the loop starts
  2. Condition: checked before each iteration; loop exits when false
  3. Update: runs after each iteration

Counting down

for (int i = 10; i > 0; i--) {
    System.out.println(i);
}

Step of 2

for (int i = 0; i < 20; i += 2) {
    System.out.println(i); // even numbers
}

Multiple variables

for (int i = 0, j = 10; i < j; i++, j--) {
    System.out.println(i + " / " + j);
}

Enhanced for-each loop (Java 5+)

For any array or anything that implements Iterable:

int[] numbers = { 10, 20, 30 };
for (int n : numbers) {
    System.out.println(n);
}

List<String> names = List.of("Alice", "Bob", "Carol");
for (String name : names) {
    System.out.println(name);
}

Map<String, Integer> ages = Map.of("Alice", 30, "Bob", 25);
for (Map.Entry<String, Integer> e : ages.entrySet()) {
    System.out.println(e.getKey() + "=" + e.getValue());
}

Shorter and safer β€” no index, no off-by-one errors. But you lose access to the index.

Classic vs enhanced: choose wisely

NeedUse
Iterate in order, read-onlyenhanced for-each
Need the indexclassic for
Iterate every nth elementclassic for
Remove while iteratingclassic Iterator with .remove()
Backwards iterationclassic for (i--)

break and continue

for (int i = 0; i < 100; i++) {
    if (i == 50) break;      // exit the loop entirely
    if (i % 2 == 0) continue; // skip to next iteration
    System.out.println(i);    // prints odd numbers 1..49
}

Labeled break for nested loops

outer:
for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 10; j++) {
        if (i * j == 42) {
            System.out.println(i + " x " + j);
            break outer; // exit both loops
        }
    }
}

Infinite loop

for (;;) {
    // no condition β€” runs forever until break or return
    if (shouldStop()) break;
}

// Or equivalently
while (true) { ... }

Iterating an index + element together

// Classic β€” cleanest when you need both
for (int i = 0; i < names.size(); i++) {
    System.out.println(i + ": " + names.get(i));
}

// Streams with IntStream
IntStream.range(0, names.size())
         .forEach(i -> System.out.println(i + ": " + names.get(i)));

Streams as an alternative

For transformations (map/filter/reduce), streams are often clearer than loops:

// Classic loop
List<Integer> squares = new ArrayList<>();
for (int n : numbers) {
    if (n > 0) squares.add(n * n);
}

// Stream version
List<Integer> squares = numbers.stream()
    .filter(n -> n > 0)
    .map(n -> n * n)
    .toList();

Use streams for pipeline-style data processing. Use classic for when:

  • You need indices
  • You need to break out early
  • Performance matters in a tight primitive loop

Common gotchas

Modifying the loop variable inside an enhanced for

for (String name : names) {
    name = name.trim(); // only reassigns the local variable
}
// names itself is unchanged β€” the reference in the list was not touched

Concurrent modification

for (String n : list) {
    if (n.isEmpty()) list.remove(n); // ❌ ConcurrentModificationException
}

// Solutions:
list.removeIf(String::isEmpty);

// Or use Iterator explicitly
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().isEmpty()) it.remove();
}

Wrong loop condition

// ❌ Off-by-one: iterates 0 through array.length INCLUSIVE β†’ IOOBE
for (int i = 0; i <= array.length; i++) { ... }

// βœ…
for (int i = 0; i < array.length; i++) { ... }

Hoisting invariants

// ❌ computes list.size() every iteration
for (int i = 0; i < list.size(); i++) { ... }

// βœ… cache when the list doesn't change
for (int i = 0, n = list.size(); i < n; i++) { ... }

The JIT usually optimizes this anyway for ArrayList, but it's still good practice and essential for custom collections.

Quick reference

PatternSyntax
Count upfor (int i = 0; i < n; i++)
Count downfor (int i = n - 1; i >= 0; i--)
Step of kfor (int i = 0; i < n; i += k)
All elementsfor (T x : collection)
Endlessfor (;;) { ... }
Stream-stylestream.forEach(x -> ...)

Four forms cover 99% of iteration needs. Pick the simplest one that expresses your intent clearly.