Explain intermediate and terminal operations in streams.

Stream operations in Java are divided into two categories: intermediate operations and terminal operations. Intermediate operations transform or filter data and return another stream, while terminal operations trigger the execution of the stream pipeline and produce a final result or side effect.

Key Points: • Intermediate operations are lazy and do not execute immediately. • Intermediate operations return a new stream for further processing. • Terminal operations initiate the actual processing of stream elements. • A stream pipeline can contain multiple intermediate operations but only one terminal operation. • Once a terminal operation is executed, the stream cannot be reused.

Intermediate Operations:

Intermediate operations process stream data step by step and return another stream.

Common Intermediate Operations:

• filter() • map() • flatMap() • distinct() • sorted() • limit() • skip() • peek()

Example:

numbers.stream() .filter(n -> n > 10) .map(n -> n * 2);

At this point, no processing occurs because no terminal operation has been invoked.

Characteristics:

• Lazy execution • Return Stream<T> • Support operation chaining • Improve efficiency by delaying execution

Terminal Operations:

Terminal operations trigger the execution of all intermediate operations and produce a result.

Common Terminal Operations:

• forEach() • collect() • count() • reduce() • findFirst() • findAny() • anyMatch() • allMatch() • noneMatch()

Example:

numbers.stream() .filter(n -> n > 10) .forEach(System.out::println);

Here, forEach() starts the actual stream processing.

Characteristics:

• Trigger execution • Produce a result or side effect • Close the stream after execution • Cannot be chained further

Example: The following example demonstrates both intermediate and terminal operations.

Code Example:

import java.util.Arrays;
import java.util.List;

public class Demo {

    public static void main(String[] args) {

        List<Integer> numbers =
                Arrays.asList(5, 10, 15, 20, 25);

        numbers.stream()

.filter(num -> num > 10) .map(num -> num * 2)

               .forEach(System.out::println);
    }
}

Output:

30
40
50

Operation Breakdown:

filter(num -> num > 10) • Intermediate Operation

map(num -> num * 2) • Intermediate Operation

forEach(System.out::println) • Terminal Operation

Execution Flow:

1. Stream is created. 2. Intermediate operations build the processing pipeline. 3. Terminal operation starts execution. 4. Results are produced. 5. Stream is closed.

Why Lazy Evaluation Matters?

Consider:

numbers.stream() .filter(num -> num > 10) .map(num -> num * 2);

Since no terminal operation exists, nothing is executed.

This improves performance because Java processes elements only when the final result is actually needed.

Interview Tip: A concise interview answer is:

"Intermediate operations such as filter() and map() return another stream and are lazily evaluated. They do not execute immediately. Terminal operations such as forEach(), collect(), and count() trigger the actual processing of the stream and produce a final result or side effect. Without a terminal operation, a stream pipeline is never executed."