Question 8 · Section 8

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

The main difference is what the mapper function returns:

Language versions: English Russian Ukrainian

Junior Level

The main difference is what the mapper function returns:

Criterion map() flatMap()
When 1->1 transformation 1->N transformation + merging
Mapper return R (one object) Stream<R> (stream of objects)
Correspondence 1-to-1 1-to-many
Nesting No Collapses nested streams

map(Function<T, R>):

  • Function returns one object of type R
  • 1-to-1 correspondence
    List<String> words = List.of("hello", "world");
    words.stream().map(String::length)  // [5, 5]
    

flatMap(Function<T, Stream<R>>):

  • Function returns a stream of objects of type R
  • 1-to-many correspondence (0, 1 or many)
    List<List<String>> nested = List.of(List.of("a", "b"), List.of("c"));
    nested.stream().flatMap(Collection::stream)  // ["a", "b", "c"]
    

Simple rule: If you have a collection inside an element — flatMap, if simple transformation — map.

Middle Level

Signatures

// map — takes T, returns R
<R> Stream<R> map(Function<T, R> mapper)

// flatMap — takes T, returns Stream<R>
<R> Stream<R> flatMap(Function<T, Stream<R>> mapper)

When to choose map

  • Data transformation (User -> UserDTO)
  • Field extraction (User -> User.getEmail())
  • Type changes but structure is linear

When to choose flatMap

  • Object contains a collection (Order -> Stream<OrderItem>)
  • External sources returning streams
  • Filtering + transformation simultaneously (returning Stream.empty())

Optional: a special difference

Optional<User> user = ...;

// map will return Optional<Optional<String>> — nesting!
Optional<Optional<String>> bad = user.map(u -> u.getEmailAsync());

// flatMap will return Optional<String> — correct
Optional<String> good = user.flatMap(u -> u.getEmailAsync());

In Optional, flatMap “collapses” nesting.

When NOT to use either

  • NOT flatMap if you just need to transform an element — use map()
  • NOT map if an element turns into a collection and you want one flat result — map() gives Stream<List<T>>, but you need Stream<T>
  • NOT either if you just need to filter — filter() is more readable

Senior Level

Performance

Memory Overhead: flatMap is more expensive — a new Stream object is created for each element. With millions of elements this creates pressure on Young Gen.

Short-Circuiting: flatMap supports short-circuiting. If .findFirst() is placed after it, execution will stop after the first element from the first nested stream.

Parallelism: flatMap in parallel streams is less efficient — splitting becomes less predictable due to the variable number of output elements.

Null Handling

  • In map: returning null creates a stream with a null element
  • In flatMap: returning null instead of a stream -> NullPointerException

Infinite Streams

flatMap can combine infinite streams, but the terminal operation will not terminate (unless there is short-circuiting).

Diagnostics

  • Type Checking: If you see Stream<List<String>> — you most likely forgot flatMap
  • Testing: Always test cases where the mapper returns an empty stream and a stream with a single null element

Interview Cheat Sheet

Must know:

  • map(Function<T, R>) — returns one object R, 1-to-1 correspondence
  • flatMap(Function<T, Stream<R>>) — returns a stream of objects, collapses nesting, 1-to-many
  • map for data transformation (User -> UserDTO), flatMap for extracting collections (Order -> Stream)
  • In Optional: map can create Optional<Optional<T>>, flatMap always returns a flat Optional<T>
  • flatMap is more expensive — creates a new Stream for each element
  • Both support short-circuiting: findFirst() will stop execution after the first element

Common follow-up questions:

  • When does map give Stream<List>? — When the mapper extracts a collection from an element but you need Stream -> use flatMap
  • null in map vs flatMap? — map passes null further, flatMap throws NullPointerException
  • When is flatMap slower? — With millions of elements due to Stream object allocation
  • Can flatMap replace filter? — Yes, by returning Stream.empty() for filtered ones, but filter is more readable

Red flags (DO NOT say):

  • “map and flatMap are interchangeable” — no, different signatures and semantics
  • “flatMap is faster than map” — no, flatMap is always more expensive due to Stream object creation
  • “map can return 0 elements” — no, map always returns 1 object (or null)
  • “flatMap guarantees order in parallel streams” — no, splitting is less predictable

Related topics:

  • [[4. What does map() operation do]]
  • [[7. What does flatMap() operation do]]
  • [[9. What are parallel streams]]
  • [[2. What is the difference between intermediate and terminal operations]]