map vs flatMap

⭐ Interview Importance: MEDIUM
⏱️ Revision Time: 5 min

TL;DR

  • map(): Transforms one element into exactly one different element. (1-to-1).
  • flatMap(): Transforms one element into a Stream of multiple elements, and then “flattens” all those separate streams into one giant, continuous stream. (1-to-Many).

Concept

If you have a List<Employee>, and you want a List<String> of their names, you use map(). You map 1 Employee to 1 String.

But what if every Employee has a List<String> of Phone Numbers? If you use map(Employee::getPhoneNumbers), you will end up with a nested List<List<String>>.

This is what flatMap() solves. It takes the Employee, extracts their List of Phone Numbers, converts that list into a mini-Stream, and then “flattens” it. The final result is a beautiful, single, un-nested List<String> containing every phone number of every employee in the company.

Examples

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

class User {
    String name;
    List<String> emails;

    public User(String name, List<String> emails) {
        this.name = name;
        this.emails = emails;
    }
}

public class FlatMapDemo {
    public static void main(String[] args) {
        List<User> users = Arrays.asList(
            new User("Alice", Arrays.asList("alice@work.com", "alice@home.com")),
            new User("Bob", Arrays.asList("bob@work.com"))
        );

        // 1. USING MAP()
        // Returns a Stream of Lists (Nested Data) -> Stream<List<String>>
        List<List<String>> nestedEmails = users.stream()
            .map(user -> user.emails) 
            .collect(Collectors.toList());
        // Result: [[alice@work.com, alice@home.com], [bob@work.com]]

        // 2. USING FLATMAP()
        // Returns a Stream of Strings (Flattened Data) -> Stream<String>
        List<String> flatEmails = users.stream()
            // We must return a STREAM from inside the flatMap!
            .flatMap(user -> user.emails.stream()) 
            .collect(Collectors.toList());
        // Result: [alice@work.com, alice@home.com, bob@work.com]
    }
}

Interview Questions

Q: When dealing with Optional, what does flatMap do?
A: Optional behaves like a Stream that contains a maximum of 1 element.
If you have an Optional<User>, and you call .map(User::getAddress), and getAddress() itself returns an Optional<Address>, you end up with an Optional<Optional<Address>>.
If you use .flatMap(User::getAddress), it automatically squashes the nested Optionals together, returning a clean Optional<Address>.

Q: Can you give a real-world use case for flatMap besides processing nested lists?
A: Yes! Processing files. If you have a List<String> containing paths to 5 text files, and you want to count the total number of words across all files.
You can stream the list of file paths. For each path, you open the file and return a Stream<String> containing all the words in that file. By using flatMap, Java seamlessly merges the 5 separate file streams into one massive stream of words, which you can then .count() perfectly.