Back to Stream: Complex Pipelines
Q5Question 5 of 8

Given a List<Company> → List<Department> → List<Employee>, extract all employee names across all companies

Key Concepts
  • Chain one flatMap per level of nesting, then a plain map for the final field projection
  • Two levels of nesting (Company → Department → Employee) require exactly two flatMap calls
  • The rule: nesting depth equals the number of flatMap calls; the final projection is a plain map
  • Track the element type at each stage to know how many flatMaps remain
  • Guard any inner collection that might be null so one bad record does not crash the entire extraction
  • For very deep or dynamic nesting, a recursive helper is clearer than a long flatMap chain

Definition

Chain one `flatMap` per level of nesting, then `map` to the field you want.
  • Multi-level flatten: chained flatMap calls remove one level of nesting each.
  • Two levels (Company → Department → Employee) need two flatMaps, then a map to the name.
  • Rule: nesting depth = number of flatMap calls; the final projection is a plain map.
  • Type tracking: after the first flatMap the stream is of Departments; after the second, Employees; after the map, Strings.
  • Null guard: protect any inner list that might be null to prevent NullPointerException.

The group head of the National Bank of India holds several company folders. Each company folder contains department folders, and each department folder contains employee files. She wants one flat list of every employee's name. Each level of opening is one flatMap.

Understanding the Concepts

How do you flatten three levels of nesting?

Each level of nesting needs its own flatMap. Starting from a stream of companies, the first flatMap turns each company into a stream of its departments. The second flatMap turns each department into a stream of its employees. Finally a map projects each employee to its name.

How do you extract all employee names across companies?

JAVA
package com.bank.hr;

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

public class MultiLevelFlatten {
    public static void main(String[] args) {
        Company nbi = new Company("National Bank of India", List.of(
            new Department("Loans", List.of(
                new Employee("Rajesh", "Loans", new BigDecimal("95000")),
                new Employee("Kavita", "Loans", new BigDecimal("78000")))),
            new Department("IT", List.of(
                new Employee("Priya", "IT", new BigDecimal("120000"))))));

        Company hdfc = new Company("HDFC", List.of(
            new Department("Finance", List.of(
                new Employee("Amit", "Finance", new BigDecimal("85000"))))));

        List<Company> companies = List.of(nbi, hdfc);

        List<String> allNames = companies.stream()
            .flatMap(c -> c.getDepartments().stream())   // Stream<Department>
            .flatMap(d -> d.getEmployees().stream())     // Stream<Employee>
            .map(Employee::getName)                       // Stream<String>
            .collect(Collectors.toList());

        System.out.println(allNames);
    }
}
TEXT
[Rajesh, Kavita, Priya, Amit]

How do you track the element type at each stage?

DIAGRAM
| Stage | Operation | Element type |
|---|---|---|
| Start | companies.stream() | Company |
| After 1st flatMap | flatMap(c -> departments) | Department |
| After 2nd flatMap | flatMap(d -> employees) | Employee |
| After map | map(Employee::getName) | String |
| After collect | toList() | List<String> |

The depth of nesting (2) equals the number of flatMap calls (2), and the final projection is a plain map because a name is a single value, not a collection.

What are common mistakes?

Mistake: using map instead of flatMap at a nesting level, producing nested streams.

JAVA
// WRONG: map preserves nesting at both levels
List<List<List<Employee>>> tripleNested = companies.stream()
    .map(c -> c.getDepartments().stream()
        .map(Department::getEmployees)
        .collect(Collectors.toList()))
    .collect(Collectors.toList());

// CORRECT: flatMap at each nesting level
List<String> flat = companies.stream()
    .flatMap(c -> c.getDepartments().stream())
    .flatMap(d -> d.getEmployees().stream())
    .map(Employee::getName)
    .collect(Collectors.toList());

What about null inner collections?

Guard any inner list that might be null to prevent a NullPointerException from crashing the whole pipeline.

JAVA
List<String> safe = companies.stream()
    .flatMap(c -> c.getDepartments() == null
        ? java.util.stream.Stream.empty()
        : c.getDepartments().stream())
    .flatMap(d -> d.getEmployees() == null
        ? java.util.stream.Stream.empty()
        : d.getEmployees().stream())
    .map(Employee::getName)
    .collect(Collectors.toList());

What about adding a filter at any level?

JAVA
// Only IT department employees earning above 100000
List<String> filtered = companies.stream()
    .flatMap(c -> c.getDepartments().stream())
    .filter(d -> "IT".equals(d.getName()))
    .flatMap(d -> d.getEmployees().stream())
    .filter(e -> e.getSalary().compareTo(new BigDecimal("100000")) > 0)
    .map(Employee::getName)
    .collect(Collectors.toList());

What about very deep or dynamic nesting?

Beyond three levels, chained flatMaps stop being readable. At that point, use a recursive helper or reconsider the data model.

What should a senior engineer know?

  • The flatMap chain is lazy: inner streams are created on demand.
  • For very large hierarchies, parallel streams can split work across companies, but very short inner streams add overhead.
  • Named intermediate variables can improve readability for complex multi-level pipelines.
  • The depth-equals-flatMaps rule is a reliable mental model for interview whiteboard problems.

What are common interview follow-up questions?

  • "How many flatMaps for four levels of nesting?" Three flatMaps plus one map.
  • "Can you filter at a specific level?" Yes, insert a filter between any two flatMaps.
  • "What about duplicate names across companies?" Add .distinct() before collecting.

Visual Overview

DIAGRAM
  List<Company>
  ┌──────────────────────────────────────────────┐
  │  NBI → [Loans, IT]                           │
  │  HDFC → [Finance]                            │
  └──────────────────────────────────────────────┘
       │  flatMap(c -> departments)
       ▼
  Stream<Department>
  [Loans, IT, Finance]
       │  flatMap(d -> employees)
       ▼
  Stream<Employee>
  [Rajesh, Kavita, Priya, Amit]
       │  map(Employee::getName)
       ▼
  Stream<String>
  ["Rajesh", "Kavita", "Priya", "Amit"]
       │  collect(toList())
       ▼
  List<String> [Rajesh, Kavita, Priya, Amit]

Key Takeaway

To flatten data nested several levels deep I chain one flatMap per level and finish with a plain map for the final field. Starting from companies, the first flatMap gives a flat stream of departments across all companies, the second flatMap gives a flat stream of all employees, and a map pulls out the names.

In my banking project at the National Bank of India this was a consolidated group headcount. We are a group with several subsidiary companies, each with departments and employees, so the data was nested three levels deep. Two flatMaps did the flattening and a map pulled out the names.

The rule I always use is: nesting depth equals the number of flatMap calls, and the final projection is a plain map. I track the element type at each stage to know how many flatMaps remain, and I guard any inner list that could be null so one bad record never crashes the whole extraction.

Remember one sentence: chain one flatMap per nesting level then a map for the final field, and the depth of nesting tells you exactly how many flatMaps you need.

---

Share
Use ← → arrow keys to navigate