Intermediate vs Terminal Operations

Naveen AnamaneniUpdated · 5 min read

Every stream operation is either intermediate or terminal. An intermediate operation, such as filter or map, returns a new stream, so more operations can be chained after it. A terminal operation, such as toList or count, produces a result (or a side effect) and ends the stream. Knowing which is which explains why a pipeline without a terminal operation does nothing at all. In this lesson, you will learn the differences and the full list of each kind.

Differences Between Intermediate and Terminal Operations

IntermediateTerminal
ReturnsA new StreamA result: a value, a collection, Optional, or void
Can be chainedYesNo; it ends the pipeline
When it runsLazily: only when a terminal operation startsImmediately: it starts the whole pipeline
How many per pipelineZero or moreExactly one
Examplesfilter, map, sortedtoList, collect, count, forEach

Intermediate Operations

MethodDescription
filter(Predicate)It keeps the elements that pass a test.
map(Function)It transforms each element.
mapToInt / mapToLong / mapToDoubleIt transforms into a primitive stream.
flatMap(Function)It replaces each element with a stream and joins them.
distinct()It removes duplicates.
sorted() / sorted(Comparator)It sorts the elements.
peek(Consumer)It looks at each element as it passes (for debugging).
limit(n) / skip(n)It keeps the first n or skips the first n.
takeWhile / dropWhile (Java 9)It keeps or drops elements while a test is true.
boxed()It turns a primitive stream into a stream of wrappers.

Terminal Operations

MethodDescription
toList() (Java 16)It collects into an unmodifiable list.
collect(Collector)It collects into a list, set, map, string and more.
forEach(Consumer)It performs an action for each element.
count()It returns the number of elements.
reduce(...)It combines all elements into one value.
min(Comparator) / max(Comparator)It returns the smallest or largest, as an Optional.
findFirst() / findAny()It returns an element, as an Optional.
anyMatch / allMatch / noneMatchIt tests the elements and returns a boolean.
toArray()It collects into an array.
sum(), average()It totals or averages a primitive stream.

Example: A Pipeline Without a Terminal Operation

Let's see an example that proves intermediate operations wait for a terminal one.

NothingHappens.java▶ Try it
import java.util.List;
import java.util.stream.Stream;
 
public class NothingHappens {
    public static void main(String[] args) {
        List<String> names = List.of("Navika", "Srihitha", "Advitha");
 
        Stream<String> pipeline = names.stream()
            .filter(n -> {
                System.out.println("  filter " + n);
                return n.length() > 6;
            });
        System.out.println("Pipeline built, nothing printed yet");
 
        long count = pipeline.count();
        System.out.println("Count: " + count);
    }
}
Output
Pipeline built, nothing printed yet
  filter Navika
  filter Srihitha
  filter Advitha
Count: 2

Explanation: Building the pipeline with filter printed nothing, because intermediate operations are lazy. Only when count() (a terminal operation) ran did the elements flow through the filter.

Example: Short-Circuiting Terminal Operations

Some terminal operations stop as soon as they know the answer:

ShortCircuit.java▶ Try it
import java.util.List;
 
public class ShortCircuit {
    public static void main(String[] args) {
        List<Integer> marks = List.of(91, 38, 85, 42);
 
        System.out.println("Any fail? "
            + marks.stream().anyMatch(m -> m < 40));
        System.out.println("All pass? "
            + marks.stream().allMatch(m -> m >= 40));
        System.out.println("None above 100? "
            + marks.stream().noneMatch(m -> m > 100));
        System.out.println("First above 80: "
            + marks.stream().filter(m -> m > 80).findFirst().get());
    }
}
Output
Any fail? true
All pass? false
None above 100? true
First above 80: 91

Explanation: anyMatch stops at 38, the first failing mark. allMatch also stops at 38, because one failure is enough to answer "no". findFirst stops at 91 without looking further.

Common Mistakes

Building a pipeline and forgetting the terminal operation. Nothing runs.

Using peek to change data. It's for looking only; use map to transform.

Calling get() on the Optional from findFirst without checking. It throws if the stream was empty.

Interview Corner

What is the difference between intermediate and terminal operations?

Intermediate operations, such as filter, map, sorted and distinct, return a new stream and are lazy: they only describe work. Terminal operations, such as collect, toList, forEach, count, reduce and findFirst, produce a result or side effect, trigger the processing of the whole pipeline, and consume the stream so it can't be reused. A pipeline has any number of intermediate operations and exactly one terminal operation.

Practice

  1. Easy: Classify each as intermediate or terminal: limit, max, distinct, collect, skip, anyMatch.
  2. Harder: Add peek calls to a pipeline of filter, map and findFirst and print what passes each stage. Explain why only some elements are printed.

Quick check: Intermediate vs terminal

4 questions
1What does an intermediate operation return?
2Which is terminal?
3How many terminal operations does a pipeline have?
4Which operation stops as soon as it finds a match?

Key Takeaways

Was this tutorial helpful?

Lesson 315 of 482