Stream.peek() in Java 8
Fast recall
Quick revision
Core answer
Whether the candidate knows peek and its proper use case as a debugging tool, not a production side-effect mechanism
Using peek for side effects in production code instead of forEach at the end, or thinking peek always executes
Explain that peek relies on the stream being consumed — if no terminal operation is called, peek never runs because of lazy evaluation
Interview-ready guide
Interview answer
peek is a lazy intermediate operation that accepts a Consumer and returns a stream with the same elements. Its main purpose is observing a pipeline while debugging—for example, placing one peek before a filter and another after it to see which values survive.
A terminal operation is needed to consume the pipeline, but even that does not guarantee every peek action will run. Stream implementations may skip stages or element traversal when a terminal result can be computed without them, and short-circuit operations deliberately stop early. Therefore a payment, audit write, counter update, or other required effect must not live in peek. If performing an action is the terminal purpose, use an appropriate terminal operation such as forEach; if ordering matters, understand forEachOrdered and the stream's encounter order.
Stream.peek() in Java 8
Direct Answer
peek is a lazy intermediate operation intended mainly for observing elements while a pipeline is being consumed, such as temporary debugging. It returns another stream and does not transform the elements. Do not use it for required business side effects: the implementation may elide stages or element visits when it can compute the terminal result without them.
Code Example
import java.util.List;
import java.util.stream.Collectors;
public class PeekDemo {
public static void main(String[] args) {
var orders = List.of(
new Order(1L, "Laptop", 500),
new Order(2L, "Mouse", 25),
new Order(3L, "Monitor", 300)
);
// Using peek to debug the pipeline
var expensiveItems = orders.stream()
.peek(o -> System.out.println("Original: " + o))
.filter(o -> o.price() > 100)
.peek(o -> System.out.println("After filter: " + o))
.map(Order::item)
.peek(item -> System.out.println("After map: " + item))
.collect(Collectors.toList());
// peek without terminal op does nothing
orders.stream()
.peek(o -> System.out.println("This won't print"));
}
}
record Order(Long id, String item, int price) {}
Common Mistakes & Pitfalls
- Using
peekfor required side effects: a stream implementation may elide stages or skip elements when the terminal result does not require them, and a short-circuit operation stops early. - Forgetting consumption: without a terminal operation, the source is not traversed and the
peekConsumer is not invoked. - Choosing
peekas the final purpose: if performing an action is the result, use a suitable terminal operation and define any ordering requirement explicitly.
Check your understanding
Answer these without looking back at the article.
- Is peek() guaranteed to run for every element?
- What is the difference between peek() and forEach()?
- Can peek() modify stream elements?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.