Back to Stream: flatMap
Q6Question 6 of 7

Given a List<Department> where each department has a List<Employee>, flatten to a single List<Employee>

Key Concepts
  • flatMap transforms each element into a stream of zero or more elements and concatenates all those streams into one
  • map(Department::getEmployees) gives Stream<List<Employee>>; flatMap gives a flat Stream<Employee>
  • flatMap naturally handles empty inner collections: an empty list contributes nothing to the output
  • The function passed to flatMap must return a Stream, not a Collection; call .stream() on the inner list
  • Guard against a null inner list by mapping null to Stream.empty() inside the flatMap function
  • flatMap is equivalent to map followed by flatten, or C#'s SelectMany

Definition

`flatMap` opens up each nested collection and pours all their elements into one single stream.
  • flatMap(Function>) transforms each element into a stream and concatenates all those streams into one flat stream.
  • map vs flatMap: map preserves nesting (Stream>); flatMap removes one level (Stream).
  • Empty collections: contribute an empty stream, which concatenates to nothing, so they disappear gracefully.
  • Null guard: if an inner list could be null, map it to Stream.empty() inside flatMap.
  • Equivalent: map + flatten, or C#'s SelectMany.

The HR head at the National Bank of India has one folder per department, and each folder contains several employee files. She wants one big combined pile of all employee files, ignoring which folder they came from. She empties every folder onto the table into a single pile. flatMap is exactly that emptying of many inner collections into one flat pile.

Understanding the Concepts

What is the difference between map and flatMap?

If you call map(Department::getEmployees) you get a Stream> where each element is itself a list. The nesting is still there. What you want is one flat stream of employees. flatMap solves this.

DIAGRAM
| Operation | Input | Output | Nesting |
|---|---|---|---|
| map(Department::getEmployees) | Stream<Department> | Stream<List<Employee>> | Preserved |
| flatMap(d -> d.getEmployees().stream()) | Stream<Department> | Stream<Employee> | Removed |

How do you flatten departments to employees?

JAVA
package com.bank.hr;

import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;

public class FlattenDepartments {
    public static void main(String[] args) {
        Department loans = new Department("Loans", List.of(
            new Employee("Rajesh", "Loans", new BigDecimal("95000")),
            new Employee("Kavita", "Loans", new BigDecimal("78000"))));

        Department it = new Department("IT", List.of(
            new Employee("Priya", "IT", new BigDecimal("120000"))));

        Department finance = new Department("Finance", List.of());

        List<Department> departments = List.of(loans, it, finance);

        List<Employee> allEmployees = departments.stream()
            .flatMap(d -> d.getEmployees().stream())
            .collect(Collectors.toList());

        allEmployees.forEach(e ->
            System.out.println(e.getName() + " (" + e.getDepartment() + ")"));
    }
}
TEXT
Rajesh (Loans)
Kavita (Loans)
Priya (IT)

Note that Finance had no employees and simply contributed nothing to the result.

How does flatMap handle empty collections?

A department with no employees contributes an empty stream, which concatenates to nothing. It simply disappears from the result without any special handling. This is cleaner than a nested loop where you would have to guard against empty or null lists.

What are common mistakes?

Mistake: using map instead of flatMap and getting a list of lists.

JAVA
// WRONG: map preserves nesting
List<List<Employee>> nested = departments.stream()
    .map(Department::getEmployees)
    .collect(Collectors.toList());  // List<List<Employee>>!

// CORRECT: flatMap removes one level of nesting
List<Employee> flat = departments.stream()
    .flatMap(d -> d.getEmployees().stream())
    .collect(Collectors.toList());  // List<Employee>

What about null inner lists?

If the data allows a null inner list, calling .stream() on null throws NullPointerException. Guard it.

JAVA
// WRONG: null employee list crashes
List<Employee> unsafe = departments.stream()
    .flatMap(d -> d.getEmployees().stream()) // NPE if list is null!
    .collect(Collectors.toList());

// CORRECT: null-safe flatMap
List<Employee> safe = departments.stream()
    .flatMap(d -> d.getEmployees() == null
        ? java.util.stream.Stream.empty()
        : d.getEmployees().stream())
    .collect(Collectors.toList());

How is flatMap equivalent to map plus flatten?

JAVA
// Two-step equivalent of flatMap
Stream<List<Employee>> mapped = departments.stream()
    .map(Department::getEmployees);        // Stream<List<Employee>>

Stream<Employee> flattened = mapped
    .flatMap(List::stream);                // Stream<Employee>

// One-step with flatMap
Stream<Employee> direct = departments.stream()
    .flatMap(d -> d.getEmployees().stream());

What about chaining flatMap with filter and map?

JAVA
// Flatten, then filter and map in the same pipeline
List<String> highEarnerNames = departments.stream()
    .flatMap(d -> d.getEmployees().stream())
    .filter(e -> e.getSalary().compareTo(new BigDecimal("90000")) > 0)
    .map(Employee::getName)
    .collect(Collectors.toList());

System.out.println(highEarnerNames);
TEXT
[Rajesh, Priya]

What should a senior engineer know?

  • flatMap is lazy: the inner streams are created on demand as elements are pulled.
  • flatMap can be chained for multi-level nesting: each level needs one flatMap.
  • On parallel streams, flatMap can split work across inner streams, but very short inner streams add overhead.
  • The key interview discriminator is: map for one-to-one, flatMap for one-to-many with flattening.

What are common interview follow-up questions?

  • "What if there are three levels of nesting?" Chain two flatMaps, then a map for the final field.
  • "What is the equivalent in SQL?" A JOIN that expands rows.
  • "What is the equivalent in C#?" SelectMany.

Visual Overview

DIAGRAM
  List<Department>
  ┌──────────────────────────────────────┐
  │ Loans   → [Rajesh, Kavita]           │
  │ IT      → [Priya]                    │
  │ Finance → []                         │
  └──────────────────────────────────────┘
       │
       │  flatMap(d -> d.getEmployees().stream())
       │
       │  Loans:   Rajesh → Kavita →
       │  IT:      Priya →
       │  Finance: (empty, nothing added)
       ▼
  Stream<Employee>
  [Rajesh, Kavita, Priya]
       │
       │  collect(toList())
       ▼
  List<Employee> [Rajesh, Kavita, Priya]

Key Takeaway

To flatten a nested collection I use flatMap, which turns each element into a stream and then concatenates all those streams into one flat stream. The crucial contrast is with map: if I mapped each department to its employee list I would get a stream of lists with the nesting still there, but flatMap pours all the inner streams into a single flat stream of employees.

In my banking project at the National Bank of India this was for a bank-wide survey. The HR data was organised as a list of departments, each holding its own list of employees. I needed one flat list of everyone, and flatMap gave exactly that.

flatMap handles edge cases gracefully: a department with no employees becomes an empty stream that contributes nothing, and I guard against null inner lists by mapping null to Stream.empty(). The key interview discriminator is: map for one-to-one, flatMap for one-to-many with flattening.

Remember one sentence: flatMap(d -> d.getEmployees().stream()) flattens one level of nesting, turning a stream of lists into a single flat stream.

---

Share
Use ← → arrow keys to navigate