Back to Stream Practice: Employee Analysis
Q14Question 14 of 43

Merge two employee streams into one using Stream.concat and deduplicate the result

Key Concepts
  • Stream.concat(a, b) creates a lazily concatenated stream: all elements of a in order, then all of b
  • Deduplication after concat uses toMap(getId, identity, mergeFunction) or the distinctByKey filter pattern
  • concat preserves encounter order: the result is ordered if both inputs are ordered
  • For three or more streams, use Stream.of(s1, s2, s3).flatMap(Function.identity()) instead of nested concat
  • Deeply nested concat calls build a lopsided tree that can degrade traversal performance
  • concat is lazy: it does not copy elements; it just chains the two streams

Definition

Stream.concat(a, b) joins two streams into one (all of a then all of b), and you follow with toMap or distinctByKey to deduplicate by employee ID.
  • Stream.concat(a, b) – returns a lazily concatenated stream: all elements of a in encounter order, then all elements of b.
  • Deduplication – uses toMap(Employee::getId, identity, (first, second) -> first) or the distinctByKey filter pattern.
  • Ordering – the result is ordered if both inputs are ordered.
  • For 3+ streams – use Stream.of(s1, s2, s3).flatMap(Function.identity()) to avoid nested concat.
  • Lazy – concat does not copy elements; it chains the two underlying spliterators.

Priya at the National Bank of India has two boxes of employee files: one from the Mumbai branch and one from the Delhi branch. Stream.concat empties the Mumbai box first, then the Delhi box, into a single line. Some employees transferred between branches and appear in both. She deduplicates by checking the employee ID stamped on each file.

Understanding the Concepts

What does this question ask?

The interviewer wants you to combine two streams and handle duplicates. This tests Stream.concat, deduplication patterns, and awareness of when to use concat vs flatMap.

How do you merge two lists with Stream.concat?

JAVA
package com.bank.service;

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

public class ConcatDemo {
    public static void main(String[] args) {
        List<Employee> mumbai = List.of(
            new Employee("E001", "Rajesh",  "TRADING"),
            new Employee("E002", "Priya",   "RISK"),
            new Employee("E003", "Amit",    "OPERATIONS"));

        List<Employee> delhi = List.of(
            new Employee("E002", "Priya",   "RISK"),
            new Employee("E004", "Kavita",  "COMPLIANCE"),
            new Employee("E005", "Suresh",  "OPERATIONS"));

        // Concat: all Mumbai first, then all Delhi
        List<Employee> merged = Stream.concat(
                mumbai.stream(), delhi.stream())
            .toList();

        System.out.println("Merged size: " + merged.size());
        merged.forEach(e -> System.out.println(e.getId() + ": " + e.getName()));
    }
}
TEXT
Merged size: 6
E001: Rajesh
E002: Priya
E003: Amit
E002: Priya
E004: Kavita
E005: Suresh

E002 (Priya) appears twice because concat does not deduplicate.

How do you deduplicate after concat?

JAVA
// Method 1: toMap with merge function
Map<String, Employee> dedupMap = Stream.concat(
        mumbai.stream(), delhi.stream())
    .collect(Collectors.toMap(
        Employee::getId,
        e -> e,
        (first, second) -> first));  // keep Mumbai (first) on conflict

List<Employee> deduped = new ArrayList<>(dedupMap.values());

// Method 2: distinctByKey filter
List<Employee> deduped2 = Stream.concat(
        mumbai.stream(), delhi.stream())
    .filter(distinctByKey(Employee::getId))
    .toList();

How is concat different from flatMap for combining streams?

DIAGRAM
| Feature | Stream.concat(a, b) | flatMap(identity) |
|---|---|---|
| Number of streams | Exactly 2 | Any number |
| Syntax | Stream.concat(s1, s2) | Stream.of(s1, s2, s3).flatMap(s -> s) |
| Nesting | Deeply nested for 3+ streams | Flat always |
| Performance | Lopsided tree degrades for many levels | Flat and efficient |
| Best for | Combining exactly 2 streams | Combining 3+ streams |
JAVA
// Two streams: concat is clear
Stream<Employee> two = Stream.concat(mumbai.stream(), delhi.stream());

// Three or more: flatMap is better
List<Employee> pune = List.of(new Employee("E006", "Neha", "IT"));
Stream<Employee> three = Stream.of(
        mumbai.stream(), delhi.stream(), pune.stream())
    .flatMap(Function.identity());

// WRONG: deeply nested concat for many streams
Stream<Employee> nested = Stream.concat(
    Stream.concat(mumbai.stream(), delhi.stream()),
    pune.stream());  // works but builds lopsided tree

What are common mistakes?

Mistake 1: reusing streams after concat.

JAVA
// WRONG: streams can only be consumed once
Stream<Employee> mumbaiStream = mumbai.stream();
Stream<Employee> merged1 = Stream.concat(mumbaiStream, delhi.stream());
merged1.forEach(System.out::println);

// This throws IllegalStateException: stream already operated on
Stream<Employee> merged2 = Stream.concat(mumbaiStream, delhi.stream());

Mistake 2: expecting concat to deduplicate automatically.

JAVA
// WRONG assumption: concat does NOT remove duplicates
List<Employee> wrong = Stream.concat(mumbai.stream(), delhi.stream())
    .toList();  // still has duplicate E002!

// CORRECT: explicitly deduplicate
List<Employee> correct = Stream.concat(mumbai.stream(), delhi.stream())
    .filter(distinctByKey(Employee::getId))
    .toList();

What about preserving order during dedup?

toMap with HashMap does not preserve insertion order. Use LinkedHashMap to maintain the concat order.

JAVA
LinkedHashMap<String, Employee> ordered = Stream.concat(
        mumbai.stream(), delhi.stream())
    .collect(Collectors.toMap(
        Employee::getId,
        e -> e,
        (first, second) -> first,
        LinkedHashMap::new));

List<Employee> orderedList = new ArrayList<>(ordered.values());
// [E001, E002, E003, E004, E005] in concat order

What should a senior engineer know?

  • concat creates a lazily concatenated spliterator. No elements are copied until the terminal operation consumes them.
  • If either input stream is parallel, the resulting stream is parallel.
  • For very large merges, consider database-level UNION instead of in-memory concat.
  • The distinctByKey pattern with ConcurrentHashMap is parallel-safe; the toMap approach with a merge function is also parallel-safe.

What are common interview follow-up questions?

  • "How do you merge and sort?" Chain sorted() after concat: Stream.concat(a, b).sorted(comparing(salary)).
  • "How do you merge with priority (take newer record)?" Use a merge function that checks a timestamp field.
  • "What is the time complexity?" O(n + m) for concat plus O(n + m) for dedup, so O(n + m) overall.

Visual Overview

DIAGRAM
  Mumbai stream              Delhi stream
  [E001, E002, E003]        [E002, E004, E005]
       │                          │
       └──────────┬───────────────┘
                  ▼
          Stream.concat()
  [E001, E002, E003, E002, E004, E005]
                  │
                  │  toMap(id, e, keep-first)
                  ▼
  ┌───────────────────────────────────┐
  │  E001 → Rajesh (Mumbai)          │
  │  E002 → Priya (Mumbai, kept)     │
  │  E003 → Amit (Mumbai)            │
  │  E004 → Kavita (Delhi, new)      │
  │  E005 → Suresh (Delhi, new)      │
  └───────────────────────────────────┘

Key Takeaway

Stream.concat joins exactly two streams into one, emitting all elements of the first in order followed by all elements of the second. The result is lazy: no elements are copied until the terminal operation consumes them.

After concat, duplicates remain because concat does not deduplicate. Use toMap(getId, identity, mergeFunction) to collect into a Map where duplicate IDs are resolved by the merge function, or use the distinctByKey filter pattern for inline deduplication.

For three or more streams, avoid deeply nested concat calls which build a lopsided tree. Instead use Stream.of(s1, s2, s3).flatMap(Function.identity()) which keeps the combination flat and efficient.

Remember one sentence: concat joins two streams lazily in order but does not deduplicate, so follow it with toMap or distinctByKey to handle overlapping IDs.

---

Share
Use ← → arrow keys to navigate