WalkthroughDifficulty: Intermediate1 min

Watching a Pipeline Touch Three Elements Out of a Thousand

Laziness is easy to state and hard to believe, so here is a pipeline that reports what it does. A thousand elements, a filter, a map and a findFirst.

java
Optional<String> first = numbers.stream()
        .peek(n -> System.out.println("testing " + n))
        .filter(n -> n % 300 == 0)
        .map(n -> "found " + n)
        .findFirst();

What the pipeline actually does, element by element

Step 1 of 5

findFirst asks for one element

The terminal operation drives everything. It does not ask the source for a thousand elements — it asks for one, and the whole chain above it runs for that one element before the second is ever fetched.

Element 1 goes through the whole chain

text
testing 1

peek prints, filter rejects it, and map is never reached for this element. A rejected element stops where it was rejected.

Elements 2 to 299 do the same

Each one is fetched, printed and rejected in turn. Nothing accumulates: at no point is there a list of filtered elements waiting for map.

Element 300 passes the filter and reaches map

text
testing 300

This is the only element map is ever called on. The transformation runs once, not three hundred times and not a thousand.

findFirst has what it needs and stops

Elements 301 to 1000 are never fetched and never printed. Three hundred peek calls, three hundred filter calls, one map call — for a source of a thousand.

The run to remember: the terminal operation pulls, one element at a time, through the whole chain. That is why findFirst, anyMatch and limit can stop early, and why putting filter before map does less work than the other way round.