Java 8 Streams — A Deep Dive into Internal Working with Tricky Q & A.

A cup of JAVA coffee with NeeSri
10 min readJan 19, 2025

--

Java 8 Streams API is a powerful abstraction that allows developers to process collections functionally, declaratively, and efficiently. Understanding how streams work internally helps optimize performance and avoid pitfalls.

1. What is a Stream in Java?

A Stream is a sequence of data elements that allows various operations like filtering, mapping, reducing, and collecting, without modifying the original data source.

Key Characteristics:

Does not store data (works on a data source like List, Set, or Array).
Supports functional-style operations (like map(), filter(), reduce()).
Processes data lazily (operations execute only when required).
Can be sequential or parallel (improves performance for large datasets).

Java 8 Stream Flow

A Stream pipeline consists of:
1️⃣ Source → (Collection, Arrays, I/O, etc.)
2️⃣ Intermediate Operations → (map, filter, sorted, distinct, etc.)
3️⃣ Terminal Operation → (collect, forEach, reduce, count, etc.)

How to Create a Stream in Java 8?

There are multiple ways to create a stream:

1. From a Collection (List, Set, etc.)

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Stream<String> nameStream = names.stream();

2. From an Array

String[] words = {"Java", "Stream", "API"};
Stream<String> wordStream = Arrays.stream(words);

3. Using Stream.of()

Stream<Integer> numberStream = Stream.of(1, 2, 3, 4, 5);

4. Using Stream.generate() (Infinite Stream)

Stream<Double> randomNumbers = Stream.generate(Math::random).limit(5);
randomNumbers.forEach(System.out::println);

5. Using Stream.iterate() (Infinite Stream)

Stream<Integer> evenNumbers = Stream.iterate(2, n -> n + 2).limit(5);
evenNumbers.forEach(System.out::println);

Types of Stream Operations

Stream operations are classified into two types:

1️⃣ Intermediate Operations (Transformations)

> These return another stream and can be chained together. They are lazy, meaning they execute only when a terminal operation is called

2️⃣ Terminal Operations (Final Results)

> These produce the final result and end the stream pipeline.

For more details read it https://neesri.medium.com/java-8-stream-intermediate-and-terminal-operations-with-q-a-d2a0ae666b96

1.1 How is a Stream Different from Collections?

2. How Streams Work Internally?

A Stream works in three steps:

Step 1: Creating a Stream (Source)

A stream is created from a data source, such as a Collection, Array, or I/O channel.

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

// Creating a stream from a List
Stream<String> nameStream = names.stream();

Internally, the stream does not copy elements; it just wraps the data source.

Step 2: Intermediate Operations (Processing Layer)

Intermediate operations transform the stream into another stream. These operations do not execute immediately (lazy execution).

Stream<String> processedStream = nameStream
.filter(name -> name.length() > 3)
.map(String::toUpperCase)
.sorted();

🛠 Internal Working:

  1. filter() creates a FilterOp object (Predicate-based filtering).
  2. map() creates a MapOp object (applies a transformation).
  3. sorted() creates a SortOp object (sorts elements in-memory).

Each operation wraps the previous operation, forming a processing pipeline.

Step 3: Terminal Operation (Execution)

A terminal operation executes the stream and produces a final result.

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

Tricky and Tough Java 8 Stream Interview Questions and Answers

Java 8 Streams API is a common topic in MNC interviews, often with tricky questions to test deep understanding. Below are some expert-level questions with detailed explanations.

1. Does filter() execute for all elements before findFirst()?

💡 Question:
Consider the following code:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

String result = names.stream()
.filter(name -> {
System.out.println("Checking: " + name);
return name.startsWith("C");
})
.findFirst()
.orElse("Not found");

System.out.println("Result: " + result);

🛠 What will be the output?

🔍 Answer:

Checking: Alice
Checking: Bob
Checking: Charlie
Result: Charlie

📌 Explanation:

  • The filter() operation is lazy and does not process all elements beforehand.
  • As soon as findFirst() finds the first matching element (Charlie), the stream stops processing further elements.
  • This demonstrates short-circuiting behavior in streams.

2. What happens if sorted() is used before filter()?

💡 Question:

List<Integer> numbers = Arrays.asList(5, 1, 4, 2, 3);

List<Integer> result = numbers.stream()
.sorted()
.filter(n -> n > 2)
.collect(Collectors.toList());

System.out.println(result);

🛠 What will be the output?

🔍 Answer:

[3, 4, 5]

📌 Explanation:

  • sorted() sorts the elements before filtering.
  • If filter() was applied before sorting, fewer elements would be processed in sorting.
  • Optimization Tip: Apply filter() before sorted() to improve performance, especially for large lists.

3. Can a stream be reused after a terminal operation?

💡 Question:

Stream<String> stream = Stream.of("A", "B", "C");
stream.forEach(System.out::println);
stream.forEach(System.out::println);

🛠 Will this code run successfully?

🔍 Answer:

A
B
C
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed

📌 Explanation:

  • A stream cannot be reused after a terminal operation (e.g., forEach()).
  • Once consumed, it is closed and throws an IllegalStateException.

Solution: Create a new stream instead of reusing the old one.

Stream<String> stream = Stream.of("A", "B", "C");
stream.forEach(System.out::println);

stream = Stream.of("A", "B", "C"); // New stream
stream.forEach(System.out::println);

4. What happens when limit() is used with sorted()?

💡 Question:

List<Integer> numbers = Arrays.asList(5, 1, 4, 2, 3);

List<Integer> result = numbers.stream()
.limit(3)
.sorted()
.collect(Collectors.toList());

System.out.println(result);

🛠 What will be the output?

🔍 Answer:

[1, 4, 5]

📌 Explanation:

  • limit(3) first selects three elements (5, 1, 4).
  • Then sorted() sorts only those three elements, not the full list.
  • Optimization Tip: Apply sorted() before limit() for better performance.

Better Code for Efficient Sorting

List<Integer> result = numbers.stream()
.sorted()
.limit(3) // Now only the top 3 elements are selected
.collect(Collectors.toList());

System.out.println(result); // Output: [1, 2, 3]

5. Why does findAny() return different results in parallel streams?

💡 Question:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

String result = names.parallelStream()
.findAny()
.orElse("Not found");

System.out.println(result);

🛠 Why does the output vary?

🔍 Answer:

  • findAny() returns any element from the stream and is not guaranteed to be the first element.
  • In sequential streams, findAny() behaves like findFirst().
  • In parallel streams, elements are processed in chunks, so the order is unpredictable.
  • 📌 Key Takeaway: Use findFirst() instead of findAny() if order matters.

6. Can parallelStream() degrade performance?

💡 Question:
Which operation will be slower when using parallelStream() instead of stream()?

🔍 Answer:

List<Integer> numbers = IntStream.range(1, 1_000_000)
.boxed()
.collect(Collectors.toList());

long start = System.nanoTime();
int sum = numbers.parallelStream().reduce(0, Integer::sum);
long end = System.nanoTime();

System.out.println("Time taken: " + (end - start));

📌 Explanation:

  • parallelStream() creates multiple threads.
  • But simple operations like sum, min, max do not benefit from parallel execution.
  • Overhead of context switching makes parallel streams slower for small datasets.
  • Parallel streams work best for CPU-intensive tasks like sorting or large computations.

7. What will happen if Collectors.toMap() has duplicate keys?

💡 Question:

List<String> words = Arrays.asList("apple", "banana", "apple");

Map<String, Integer> wordLengthMap = words.stream()
.collect(Collectors.toMap(
word -> word,
word -> word.length()
));

System.out.println(wordLengthMap);

🛠 What will be the output?

🔍 Answer:

Exception in thread "main" java.lang.IllegalStateException: Duplicate key apple

📌 Explanation:

  • Collectors.toMap() does not allow duplicate keys.
  • Since "apple" appears twice, it throws an IllegalStateException.

Solution: Handle duplicates using merge function.

Map<String, Integer> wordLengthMap = words.stream()
.collect(Collectors.toMap(
word -> word,
word -> word.length(),
(oldValue, newValue) -> oldValue // Keep the first value
));

System.out.println(wordLengthMap); // Output: {apple=5, banana=6}

8. Will distinct() work correctly in parallel streams?

💡 Question:

List<Integer> numbers = Arrays.asList(1, 2, 3, 1, 2, 3, 4);

List<Integer> uniqueNumbers = numbers.parallelStream()
.distinct()
.collect(Collectors.toList());

System.out.println(uniqueNumbers);

🛠 Will this work correctly?

🔍 Answer:

  • Yes, distinct() works with parallel streams, but order is not guaranteed.
  • HashSet is used internally to remove duplicates.
  • If order matters, use sequential streams.

For Ordered Output:

List<Integer> uniqueNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());

9. Why does count() execute even without terminal operations like forEach()?

💡 Question

Stream<String> stream = Stream.of("A", "B", "C");
long count = stream.filter(s -> {
System.out.println("Processing: " + s);
return true;
}).count();

System.out.println("Count: " + count);

🛠 What will be the output?

🔍 Answer:

Processing: A
Processing: B
Processing: C
Count: 3

📌 Explanation:

  • count() is a terminal operation that triggers execution.
  • Unlike forEach(), it does not require storing elements; it only counts.
  • Even though the filter condition is always true, it still processes each element.

Optimization Tip: Avoid unnecessary operations if only counting.

long count = Stream.of("A", "B", "C").count(); // More efficient

10. Can map() change the number of elements in a stream?

💡 Question:

List<String> words = Arrays.asList("apple", "banana", "cherry");

List<Character> firstLetters = words.stream()
.map(word -> word.charAt(0)) // Extract first character
.collect(Collectors.toList());

System.out.println(firstLetters);

🛠 What will be the output?

🔍 Answer:

[a, b, c]

📌 Explanation:

  • map() always returns the same number of elements as the input stream.
  • It transforms each element one-to-one.

What if we want to change the element count?
Use flatMap() instead of map().

List<String> letters = words.stream()
.flatMap(word -> Arrays.stream(word.split(""))) // Splitting each word into letters
.collect(Collectors.toList());

System.out.println(letters);
// Output: [a, p, p, l, e, b, a, n, a, n, a, c, h, e, r, r, y]

11. What is the difference between map() and flatMap()?

💡 Question:
Consider this example

List<List<Integer>> numbers = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5),
Arrays.asList(6, 7, 8)
);

// Using map()
List<Stream<Integer>> mappedList = numbers.stream()
.map(List::stream)
.collect(Collectors.toList());

// Using flatMap()
List<Integer> flatMappedList = numbers.stream()
.flatMap(List::stream)
.collect(Collectors.toList());

System.out.println("Mapped: " + mappedList);
System.out.println("FlatMapped: " + flatMappedList);

🛠 What will be the output?

🔍 Answer:

Mapped: [java.util.stream.ReferencePipeline$Head@5f184fc6, java.util.stream.ReferencePipeline$Head@3f99bd52, java.util.stream.ReferencePipeline$Head@525e04c4]
FlatMapped: [1, 2, 3, 4, 5, 6, 7, 8]

📌 Explanation:

  • map() returns a stream of streams (Stream<Stream<Integer>>), which is not useful.
  • flatMap() flattens nested streams into a single stream (Stream<Integer>).

Use flatMap() when working with nested structures (List of Lists, Arrays of Arrays, etc.).

12. Why does peek() not print anything in this example?

💡 Question:

Stream.of("A", "B", "C")
.peek(System.out::println)
.map(String::toLowerCase);

🛠 Why is there no output?

🔍 Answer:

  • Streams are lazy.
  • peek() is an intermediate operation and does nothing unless a terminal operation is present.

Fix the issue by adding a terminal operation:

Stream.of("A", "B", "C")
.peek(System.out::println)
.map(String::toLowerCase)
.collect(Collectors.toList()); // Now execution happens

13. What will be the output when using parallelStream() with forEach()?

💡 Question:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

names.parallelStream()
.forEach(System.out::println);

🛠 Will this print names in order?

🔍 Answer:

Bob
David
Alice
Charlie

Or any other order — it is unpredictable.)

📌 Explanation:

  • forEach() with parallelStream() does not guarantee order.
  • Multiple threads process elements simultaneously.

To preserve order, use forEachOrdered():

names.parallelStream()
.forEachOrdered(System.out::println);

✔ Output will always be:

Alice
Bob
Charlie
David

14. Why does min() sometimes return an unexpected value?

💡 Question:

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

Optional<Integer> min = numbers.stream()
.min((a, b) -> 1); // Incorrect Comparator

System.out.println(min.get());

🛠 What will be the output?

🔍 Answer:

10 OR 30

📌 Explanation:

  • The comparator (a, b) -> 1 always returns 1, meaning any element can be considered the "minimum".
  • This is incorrect behavior.

Use Comparator.naturalOrder() instead:

Optional<Integer> min = numbers.stream()
.min(Comparator.naturalOrder());
System.out.println(min.get()); // Correct output: 5

15. How does groupingBy() work internally?

💡 Question:

List<String> words = Arrays.asList("apple", "banana", "cherry", "avocado");

Map<Character, List<String>> groupedWords = words.stream()
.collect(Collectors.groupingBy(word -> word.charAt(0)));

System.out.println(groupedWords);

🛠 What will be the output?

🔍 Answer:

{a=[apple, avocado], b=[banana], c=[cherry]}

📌 Explanation:

  • groupingBy() uses a HashMap internally.
  • It groups elements based on a classifier function (first character in this case).
  • The result is a Map<Character, List<String>>.

Modify behavior by using a downstream collector:

Map<Character, Long> wordCount = words.stream()
.collect(Collectors.groupingBy(word -> word.charAt(0), Collectors.counting()));

System.out.println(wordCount);
// Output: {a=2, b=1, c=1}

16. Are Java 8 Streams Lazy? If Yes, How?

🔍 Answer:
Yes, Java 8 Streams are lazy, meaning operations are not executed immediately but only when a terminal operation is called.

📌 How it Works?

  • Intermediate operations (map(), filter(), sorted(), etc.) do not execute immediately.
  • Instead, they are stored in a pipeline and executed only when a terminal operation (collect(), count(), forEach()) is triggered.
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5)
.filter(n -> {
System.out.println("Filtering: " + n);
return n % 2 == 0;
});

System.out.println("No execution yet!");

// Now applying terminal operation
List<Integer> result = stream.collect(Collectors.toList());
System.out.println("Execution starts now!");

----------------Output----
No execution yet!
Filtering: 1
Filtering: 2
Filtering: 3
Filtering: 4
Filtering: 5
Execution starts now!

📌 Key Takeaway:
Operations are delayed until necessary, improving efficiency.

17. Why are Java Streams not reusable?

🔍 Answer:
Once a terminal operation (e.g., collect(), forEach()) is executed, the stream is closed and cannot be used again.

Stream<String> stream = Stream.of("Java", "Python", "C++");

stream.forEach(System.out::println); // Consumes stream

stream.forEach(System.out::println); // Throws Exception!

---------Output----
Java
Python
C++
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed

📌 Why?

  • Streams are consumed when a terminal operation is applied.
  • They do not store elements (like Collections).

Solution: Create a new stream if re-use is needed:

Stream<String> stream1 = Stream.of("Java", "Python", "C++");
stream1.forEach(System.out::println);

Stream<String> stream2 = Stream.of("Java", "Python", "C++"); // New stream
stream2.forEach(System.out::println);

18. How does Short-Circuiting Work in Streams?

🔍 Answer:
Short-circuiting means execution stops early when a result is found.

📌 Methods that Short-Circuit Execution:

Stream.of(10, 20, 30, 40, 50)
.filter(n -> {
System.out.println("Checking: " + n);
return n > 20;
})
.findFirst();

-------Output----
Checking: 10
Checking: 20
Checking: 30

📌 Key Takeaway:

  • findFirst() stops execution once it finds 30.
  • Remaining elements (40, 50) are not processed.

19. What is the difference between forEach() and forEachOrdered()?

🔍 Answer:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

System.out.println("Using forEach:");
names.parallelStream().forEach(System.out::println);

System.out.println("Using forEachOrdered:");
names.parallelStream().forEachOrdered(System.out::println);

---------Output--------
Using forEach:
Charlie
Alice
David
Bob
Using forEachOrdered:
Alice
Bob
Charlie
David

📌 Key Takeaway: Use forEachOrdered() when order matters, even in parallel streams.

20. What happens if Collectors.toMap() gets duplicate keys?

🔍 Answer:
By default, Collectors.toMap() throws an exception if keys are duplicated.

List<String> words = Arrays.asList("apple", "banana", "apple");

Map<String, Integer> wordMap = words.stream()
.collect(Collectors.toMap(
word -> word,
word -> word.length()
));
---------output-------
Exception in thread "main" java.lang.IllegalStateException: Duplicate key apple

Fix: Handle duplicates with merge function:

Map<String, Integer> wordMap = words.stream()
.collect(Collectors.toMap(
word -> word,
word -> word.length(),
(oldValue, newValue) -> oldValue // Keep first value
));

21. Why is distinct() sometimes slow with parallel streams?

🔍 Answer:

  • distinct() removes duplicates by internally using HashSet.
  • In parallel streams, maintaining a single HashSet across threads is inefficient.
List<Integer> numbers = Arrays.asList(1, 2, 3, 1, 2, 3, 4);

numbers.parallelStream()
.distinct()
.forEach(System.out::println);

📌 Key Takeaway: Use distinct() in sequential streams for better performance.

Happy Learning :)

--

--

Responses (1)