Back to Stream Practice: Collections & Numbers
Q11Question 11 of 11

Find the intersection and union of two lists using streams

Key Concepts
  • Intersection: filter the first list by membership in a HashSet of the second list
  • Union: Stream.concat both lists and apply distinct() to remove duplicates
  • Always build a HashSet for the lookup list; list.contains is O(n) per call, making intersection O(m*n)
  • Both operations depend on correct equals and hashCode on the element type
  • Use distinct() on the intersection result to avoid duplicates from the first list
  • For ordered results, use a LinkedHashSet-backed approach or sort after collecting

Definition

Intersection filters one list by membership in a HashSet of the other; union concatenates both and applies distinct.
  • Intersection: a.stream().filter(setB::contains).distinct().toList() keeps elements in both lists.
  • Union: Stream.concat(a.stream(), b.stream()).distinct().toList() gives every element in either list.
  • HashSet for O(1) membership checks makes intersection O(m+n) instead of O(m*n).
  • equals/hashCode must be correct on the element type for both operations.
  • distinct() removes duplicates from both the intersection and union results.

Priya at the National Bank of India compares two branches' customer ID lists. The intersection gives the shared customers who have accounts at both branches. The union gives the total unique customer base across both branches.

Understanding the Concepts

What does this question ask?

The interviewer wants set intersection and union using streams. This tests filter with set membership, Stream.concat, distinct, and the performance of the lookup collection.

How do you find the intersection?

JAVA
package com.bank.service;

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

public class IntersectionUnion {
    public static void main(String[] args) {
        List<String> branch1 = List.of(
            "C001", "C002", "C003", "C004", "C005");
        List<String> branch2 = List.of(
            "C003", "C004", "C005", "C006", "C007");

        Set<String> setB = new HashSet<>(branch2);

        List<String> intersection = branch1.stream()
            .filter(setB::contains)
            .distinct()
            .toList();

        System.out.println("Intersection: " + intersection);
    }
}
TEXT
Intersection: [C003, C004, C005]

How do you find the union?

JAVA
import java.util.stream.Stream;

List<String> union = Stream.concat(
        branch1.stream(), branch2.stream())
    .distinct()
    .toList();

System.out.println("Union: " + union);
TEXT
Union: [C001, C002, C003, C004, C005, C006, C007]

Why use a HashSet for the membership test?

| Lookup collection | contains time | Intersection time |
|---|---|---|
| List (ArrayList) | O(n) per call | O(m * n) total |
| HashSet | O(1) per call | O(m + n) total |

JAVA
// WRONG: quadratic with list.contains
List<String> slow = branch1.stream()
    .filter(branch2::contains)  // O(n) per element!
    .toList();

// CORRECT: linear with HashSet.contains
Set<String> fast = new HashSet<>(branch2);
List<String> quick = branch1.stream()
    .filter(fast::contains)  // O(1) per element
    .toList();

How do you find the difference (A minus B)?

JAVA
Set<String> setB = new HashSet<>(branch2);

List<String> difference = branch1.stream()
    .filter(id -> !setB.contains(id))
    .toList();

System.out.println("Only in branch1: " + difference);
TEXT
Only in branch1: [C001, C002]

How do you find the symmetric difference?

JAVA
Set<String> setA = new HashSet<>(branch1);
Set<String> setBB = new HashSet<>(branch2);

List<String> symmetricDiff = Stream.concat(
        branch1.stream().filter(id -> !setBB.contains(id)),
        branch2.stream().filter(id -> !setA.contains(id)))
    .toList();

System.out.println("Symmetric difference: " + symmetricDiff);
TEXT
Symmetric difference: [C001, C002, C006, C007]

What are common mistakes?

Mistake: using list.contains instead of set.contains.

JAVA
// WRONG: O(m * n) quadratic on large lists
branch1.stream().filter(branch2::contains).toList();

// CORRECT: O(m + n) with HashSet
Set<String> setB = new HashSet<>(branch2);
branch1.stream().filter(setB::contains).toList();

Mistake: forgetting that equals/hashCode must be correct.

JAVA
// If elements are custom objects without equals/hashCode,
// HashSet.contains uses Object identity, not value equality.
// Always override equals and hashCode for value types.

What about preserving order?

The intersection preserves the order of the first list. The union preserves encounter order because distinct keeps the first occurrence.

What are common interview follow-up questions?

  • "How do you count the intersection size?" Replace toList with count().
  • "How do you find common elements across three lists?" Chain two intersections: intersect(a, b) then intersect(result, c).
  • "How do you find the Jaccard similarity?" intersection.size() / union.size().

Visual Overview

DIAGRAM
  Branch 1: [C001, C002, C003, C004, C005]
  Branch 2: [C003, C004, C005, C006, C007]

  Intersection: filter(setB::contains)
  ┌──────────────────────────────────────┐
  │  C001 in setB? NO  → skip           │
  │  C002 in setB? NO  → skip           │
  │  C003 in setB? YES → keep           │
  │  C004 in setB? YES → keep           │
  │  C005 in setB? YES → keep           │
  └──────────────────────────────────────┘
  Result: [C003, C004, C005]

  Union: concat + distinct
  [C001,C002,C003,C004,C005,C003,C004,C005,C006,C007]
  → distinct → [C001,C002,C003,C004,C005,C006,C007]

Key Takeaway

Intersection filters one list by membership in a HashSet of the other, and union concatenates both streams and applies distinct. The HashSet is the single most important performance detail: filtering with a raw list's contains is quadratic, while a HashSet gives constant-time lookups.

Both operations depend on the element type having correct equals and hashCode. The intersection preserves the order of the first list, and the union preserves encounter order. At the National Bank of India, Rajesh used exactly this to compare two branches' customer ID lists for shared accounts and total unique customers.

The same pattern extends to difference (A minus B) by negating the filter, and to symmetric difference by combining both one-sided differences.

Remember one sentence: always build a HashSet for the membership test in intersection, because list.contains is O(n) per call while HashSet.contains is O(1).

---

Share
Use ← → arrow keys to navigate