🏠 Home Streams & Functional Stream Lazy Evaluation - Intermediate vs Termin...
STREAMS & FUNCTIONAL

Stream Lazy Evaluation - Intermediate vs Terminal Operations

Rule: Intermediate operations are lazy — they define what should be done, but not when it’s done. They are only evaluated when a terminal operation is invoked.

  • Intermediate operations: Return a new Stream and are lazily evaluated
  • Terminal operations: Trigger the processing of the stream and produce a result or side effect
// Intermediate operations - define the pipeline but don't execute
Stream<String> words = Stream.of("apple", "banana", "cherry", "apricot");

Stream<String> pipeline = words
    .filter(s -> {
        System.out.println("Filtering: " + s);  // This won't print yet!
        return s.startsWith("a");
    })
    .map(s -> {
        System.out.println("Mapping: " + s);    // This won't print yet!
        return s.toUpperCase();
    });

System.out.println("Pipeline created, but nothing executed yet");

// Terminal operation - triggers execution of entire pipeline
List<String> result = pipeline.collect(Collectors.toList());
// NOW the filtering and mapping print statements execute
// Output: APPLE, APRICOT

Stream Operations Reference:

Intermediate Operations (Lazy) Description Terminal Operations (Eager) Description
filter() Filters elements based on predicate collect() Collects elements into collection
map() Transforms elements forEach() Performs action on each element
flatMap() Flattens nested streams reduce() Reduces elements to single value
distinct() Removes duplicates (stateful) count() Counts elements
sorted() Sorts elements (stateful) findFirst() Finds first element
limit() Limits number of elements (stateful) findAny() Finds any element
skip() Skips first n elements (stateful) anyMatch() Checks if any element matches
peek() Performs action without consuming allMatch() Checks if all elements match
takeWhile() Takes elements while condition true noneMatch() Checks if no elements match
dropWhile() Drops elements while condition true min() Finds minimum element
    max() Finds maximum element
    toArray() Converts to array

Lazy Evaluation Example:

// This creates an infinite stream but doesn't hang!
Stream<Integer> infiniteStream = Stream.iterate(1, n -> n + 1)
    .filter(n -> n % 2 == 0)  // Intermediate - lazy
    .map(n -> n * 2);         // Intermediate - lazy

// Only when we add a terminal operation does execution begin
List<Integer> first5Even = infiniteStream
    .limit(5)                 // Intermediate - lazy
    .collect(Collectors.toList());  // Terminal - triggers execution

System.out.println(first5Even); // [4, 8, 12, 16, 20]

Stream Reuse Warning:

Stream<String> stream = Stream.of("a", "b", "c");

stream.forEach(System.out::print);  // Terminal - stream consumed
// stream.count();                  // ❌ IllegalStateException!

// Must create new stream for additional operations
Stream.of("a", "b", "c").count();   // ✅ OK - new stream

💡 Learning Tips:

  • Lazy evaluation: “Build the recipe, cook when ordered” - intermediate operations build the pipeline, terminal operations execute it
  • Stateful vs Stateless: Stateful operations (distinct, sorted, limit) may need to see all elements before producing results
  • One-time use: Streams are consumed by terminal operations and cannot be reused
  • Performance benefit: Lazy evaluation allows for optimizations like short-circuiting and fusion

Q: What happens if you chain multiple intermediate operations without a terminal operation?
A: Nothing executes — the pipeline is built but remains dormant until a terminal operation triggers evaluation.