Back to Stream: Complex Pipelines
Q2Question 2 of 8

From a List<Company>, extract all employees, filter earners > 60000, group by city, sort each group by name

Key Concepts
  • flatMap flattens nested structures: one flatMap per nesting level (company -> departments -> employees)
  • filter(salary > 60000) keeps only high earners after flattening
  • groupingBy(city) with a mapping + sorting downstream produces city -> sorted name list
  • collectingAndThen wraps the collected list with a sort to produce per-group sorted names
  • Guard nulls at each level of nesting to avoid NullPointerException
  • This composition demonstrates the full power of Streams on a genuine multi-level hierarchy

Definition

`flatMap` the nested hierarchy, `filter` by salary, then `groupingBy(city, mapping(name, sorted-collector))` produces city-to-sorted-names.
  • flatMap per nesting level flattens companies -> departments -> employees into one stream.
  • filter(salary > 60000) keeps high earners.
  • groupingBy(city) with mapping(name) + collectingAndThen(toList, sort) produces sorted name lists per city.
  • Result: Map> of city to alphabetically sorted employee names.
  • Guard nulls at each nesting level.

At National Bank of India group level, Priya opens every subsidiary company, then every department, pulls out all employees into one big pile, keeps the high earners, sorts them into city bins, and within each bin lists names in alphabetical order.

Understanding the Concepts

What makes this pipeline complex?

The data is nested three levels deep: companies contain departments which contain employees. Flattening this hierarchy, filtering, grouping, and sorting within groups requires composing multiple stream operations in a single pipeline.

How do you implement it?

JAVA
package com.bank.group;

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

public class ComplexPipeline {

    record Employee(String name, String city, BigDecimal salary) {}
    record Department(String name, List<Employee> employees) {}
    record Company(String name, List<Department> departments) {}

    public static void main(String[] args) {
        List<Company> companies = List.of(
            new Company("NBI Securities", List.of(
                new Department("TRADING", List.of(
                    new Employee("Rajesh", "Mumbai", new BigDecimal("80000")),
                    new Employee("Priya",  "Delhi",  new BigDecimal("70000")))),
                new Department("RISK", List.of(
                    new Employee("Amit",   "Mumbai", new BigDecimal("50000")),
                    new Employee("Kavita", "Chennai",new BigDecimal("65000")))))),
            new Company("NBI Insurance", List.of(
                new Department("CLAIMS", List.of(
                    new Employee("Meera",  "Mumbai", new BigDecimal("75000")),
                    new Employee("Deepak", "Delhi",  new BigDecimal("55000"))))))
        );

        Map<String, List<String>> cityToNames = companies.stream()
            .flatMap(c -> c.departments().stream())      // Company -> Department
            .flatMap(d -> d.employees().stream())         // Department -> Employee
            .filter(e -> e.salary().compareTo(new BigDecimal("60000")) > 0)
            .collect(Collectors.groupingBy(
                Employee::city,
                Collectors.collectingAndThen(
                    Collectors.mapping(Employee::name, Collectors.toList()),
                    list -> { Collections.sort(list); return list; })));

        cityToNames.forEach((city, names) ->
            System.out.println(city + ": " + names));
    }
}
TEXT
Mumbai: [Meera, Rajesh]
Delhi: [Priya]
Chennai: [Kavita]

Amit (Rs.50,000) and Deepak (Rs.55,000) are filtered out because they earn <= 60,000.

Why is flatMap the key operation?

Each level of nesting requires one flatMap. The first flatMap turns each company into its departments, and the second turns each department into its employees, flattening the entire hierarchy into a single Stream.

How does the downstream collector work?

  • The downstream combines three collectors:
  • mapping(Employee::name, toList()) extracts names into a list.
  • collectingAndThen(collector, finisher) sorts the list after collection.

The result is a sorted list of names per city.

What are common mistakes?

Mistake: Forgetting a flatMap level and getting a stream of departments instead of employees.

JAVA
// WRONG: only one flatMap, gets Stream<Department>
companies.stream()
    .flatMap(c -> c.departments().stream())  // Stream<Department>
    .filter(...)  // cannot filter Department by salary

// CORRECT: two flatMaps for two nesting levels
companies.stream()
    .flatMap(c -> c.departments().stream())   // Stream<Department>
    .flatMap(d -> d.employees().stream())      // Stream<Employee>
    .filter(e -> e.salary().compareTo(new BigDecimal("60000")) > 0)

What about null safety?

Guard nulls at each nesting level.

JAVA
companies.stream()
    .filter(c -> c.departments() != null)
    .flatMap(c -> c.departments().stream())
    .filter(d -> d.employees() != null)
    .flatMap(d -> d.employees().stream())
    .filter(e -> e.salary() != null && e.city() != null)
    ...

What about using TreeSet for uniqueness and sorting?

If you want unique names sorted, replace toList() + sort with toCollection(TreeSet::new).

What should a senior engineer know?

This pipeline demonstrates the full composability of streams: flatMap for hierarchy flattening, filter for selection, and nested collectors for grouped, transformed, sorted results. Each operation has a clear role, and the whole thing reads as a declarative data transformation rather than nested loops.

What are common interview follow-up questions?

  • "How many flatMaps for N nesting levels?" N-1 flatMaps.
  • "How do you sort by city name?" Use TreeMap::new as the map supplier in groupingBy.
  • "How do you also include the salary?" Map to a record NameSalary instead of just the name string.

Visual Overview

DIAGRAM
  Companies: [NBI Securities, NBI Insurance]
       │
       │  flatMap(departments)
       ▼
  Departments: [TRADING, RISK, CLAIMS]
       │
       │  flatMap(employees)
       ▼
  Employees: [Rajesh 80K, Priya 70K, Amit 50K, Kavita 65K, Meera 75K, Deepak 55K]
       │
       │  filter(salary > 60K)
       ▼
  [Rajesh 80K, Priya 70K, Kavita 65K, Meera 75K]
       │
       │  groupingBy(city, mapping(name, sorted))
       ▼
  Mumbai:  [Meera, Rajesh]  (sorted)
  Delhi:   [Priya]
  Chennai: [Kavita]

Key Takeaway

This pipeline demonstrates the full power of streams on a multi-level hierarchy. flatMap flattens the nesting: one flatMap per level turns companies into departments into employees. After filtering for high earners, groupingBy with a mapping and collectingAndThen downstream produces per-city sorted name lists.

At National Bank of India group level, this backs a group-wide report spanning multiple subsidiary companies. The entire transformation from nested hierarchy to grouped, filtered, sorted output happens in a single declarative pipeline.

The key insight is that each level of nesting needs one flatMap, and combining flatMap, filter, and nested collectors produces a readable, composable pipeline that replaces nested loops. Guard nulls at each level, and choose the per-group collector based on what the consumer needs.

Remember one sentence: one flatMap per nesting level flattens the hierarchy, then groupingBy with nested downstream collectors produces grouped, sorted results in a single pipeline.

---

Share
Use ← → arrow keys to navigate