When a Plain Loop Wins

Streams · lesson 28 of 42 · 4 min read

The four jobs where a for loop is still the clearer, faster and more honest answer.

Open this lesson in the learning hub

Key points

  • You need the index. IntStream.range(...).mapToObj(i -> list.get(i)) is a stream pretending to be a loop.
  • You are filling two collections at once. One loop does it in one pass; the stream version needs teeing or two passes.
  • You want to break early while keeping running state. break is one word; the stream needs takeWhile plus a fold.
  • You are mutating the source, or the body throws checked exceptions. Neither fits comfortably in a lambda.
  • Streams win on straight transform, filter and aggregate work. Mixed responsibilities usually read better as a loop.
  • Never nest three streams deep to avoid a loop. Reviewers read the loop faster, and so will you next quarter.

Example

import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        List<String> rows = List.of("ada", "grace", "alan", "edsger");

        // 1. You need the index. The stream version has to invent one.
        StringBuilder numbered = new StringBuilder();
        for (int i = 0; i < rows.size(); i++) {
            numbered.append(i + 1).append('.').append(rows.get(i)).append(' ');
        }
        System.out.println("loop index  : " + numbered.toString().trim());
        System.out.println("faked index : " + IntStream.range(0, rows.size())
                .mapToObj(i -> (i + 1) + "." + rows.get(i)).collect(Collectors.joining(" ")));

        // 2. You are filling two collections in one pass.
        List<String> shortNames = new ArrayList<>();
        List<String> longNames = new ArrayList<>();
        for (String r : rows) {
            (r.length() <= 4 ? shortNames : longNames).add(r);
        }
        System.out.println("two lists   : " + shortNames + " " + longNames);

        // 3. Stop early and keep a running total. break is plain; the stream needs two steps.
        int total = 0;
        for (String r : rows) {
            if (r.startsWith("e")) {
                break;
            }
            total += r.length();
        }
        System.out.println("loop total  : " + total);
        System.out.println("stream same : " + rows.stream()
                .takeWhile(r -> !r.startsWith("e")).mapToInt(String::length).sum());
    }
}

A stream is a tool, not a style rule. Pick whichever one the next reader will understand faster.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Streams course, and every lesson in it is listed on the Streams contents page.