What does flatMap() operation do?
Used when you have nested structures (collection of collections):
Junior Level
flatMap() is an intermediate operation that performs two steps:
- Map: transforms each element into a collection/stream
- Flat: joins all these streams into one long stream
Analogy: like taking several stacks of paper and merging them into one.
Used when you have nested structures (collection of collections):
List<List<String>> nested = List.of(
List.of("a", "b"),
List.of("c", "d")
);
// flatMap "flattens" into a single stream
List<String> flat = nested.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
// Result: ["a", "b", "c", "d"]
// Extracting all tags from posts
List<String> allTags = posts.stream()
.flatMap(post -> post.getTags().stream())
.distinct()
.collect(Collectors.toList());
Simple rule: map = 1-to-1, flatMap = 1-to-many.
Middle Level
Process visualization
- Original stream:
[A, B] - Mapping:
A -> [a1, a2],B -> [b1, b2] - Without flatMap (just map):
[[a1, a2], [b1, b2]]— stream of streams - With flatMap:
[a1, a2, b1, b2]— single stream
When to use flatMap
- Object contains a collection (
Order -> Stream<OrderItem>) - Working with external sources returning streams
- Need to filter and transform simultaneously (return
Stream.empty()for unwanted)
Optional.flatMap
In Optional, flatMap is used for chains of safe calls returning Optional:
Optional<String> email = user
.flatMap(User::getContact)
.flatMap(Contact::getEmail);
Avoids Optional<Optional<String>>.
Primitive flatMap
Always use flatMapToInt, flatMapToLong, flatMapToDouble to avoid autoboxing:
int[] allNumbers = lists.stream()
.flatMapToInt(list -> list.stream().mapToInt(Integer::intValue))
.toArray();
When NOT to use flatMap
- Simple filtering — use
filter(), no need to “unroll” elements - One-to-one — use
map(), flatMap is redundant - Just need an action for each —
forEach()is cleaner
Senior Level
Resource management in flatMap
Critical point: if the function opens a resource (Files.lines(path)), that resource must be closed.
Stream API guarantees close() is called on all streams created inside flatMap, if the main stream is closed (via try-with-resources):
try (Stream<Path> paths = Files.list(dir)) {
paths.flatMap(path -> {
try {
return Files.lines(path);
} catch (IOException e) {
return Stream.empty();
}
}).forEach(System.out::println);
}
// flatMap guarantees closing of nested streams: // when the outer stream closes, ALL nested streams close. // This works through the AutoCloseable mechanism of Stream API.
Lazy Evaluation and Short-circuiting
flatMap is fully lazy. If limit(1) is placed after flatMap, as soon as the first element from the first inner stream is obtained — the remaining streams will not even be created.
Overhead
Each flatMap call creates a new stream object. In extremely loaded loops this can create memory pressure compared to a simple for.
Edge Cases
- Empty Streams: If the mapper returns
Stream.empty()— the element disappears (analogous tofilter) - Null Streams: If the mapper returns
nullinstead of a stream — you get aNullPointerException. Always returnStream.empty()
Parallelism
flatMap in parallel streams works less efficiently than map — task splitting becomes less predictable due to the variable number of output elements.
Diagnostics
Debugging flatMap is complex. Use peek() inside the flatMap lambda to see which element caused the problem.
Interview Cheat Sheet
Must know:
flatMap= Map (each element -> stream) + Flat (merge all streams into one)- map = 1-to-1, flatMap = 1-to-many
- Returns
Stream<R>, mapper takesTand returnsStream<R> - Optional.flatMap avoids nesting of
Optional<Optional<T>> - For primitives: flatMapToInt, flatMapToLong, flatMapToDouble — avoid autoboxing
- Mapper must never return null — only
Stream.empty()
Common follow-up questions:
- When does flatMap filter elements? — When the mapper returns
Stream.empty()for unwanted ones - How does flatMap manage resources (Files.lines)? — Automatically closes nested streams when the outer stream closes
- flatMap + limit(1) — how many streams are created? — Only the first inner one, the rest are not created (laziness)
- Why is flatMap less efficient in parallelism? — Variable number of output elements complicates splitting
Red flags (DO NOT say):
- “flatMap and map are the same” — map returns one object, flatMap returns a stream
- “flatMap mapper can return null” — no, it will be a NullPointerException
- “flatMap guarantees element order” — order depends on the source, not flatMap
- “flatMap always costs more memory than map” — not always, depends on the number of output elements
Related topics:
- [[8. What is the difference between map() and flatMap()]]
- [[4. What does map() operation do]]
- [[3. What does filter() operation do]]
- [[2. What is the difference between intermediate and terminal operations]]