Set operations: union, intersection and difference
Set algebra in Java: union, intersection, difference, symmetric difference, subset checks, disjoint checks, copy-first safety, streams, Guava views, range views and EnumSet bit operations.
On this page
🔵 What are set operations?
Set operations are the collection version of Venn diagrams: union means everything in either set, intersection means only the overlap, and difference means what remains after removing another set. If one service has product IDs {A, B, C} and another has {B, C, D}, the union is {A, B, C, D}, the intersection is {B, C}, and the first-minus-second difference is {A}.
For this lesson, we'll focus on Java's built-in operations (addAll, retainAll, removeAll, containsAll, Collections.disjoint), how to use them without mutating your inputs, stream alternatives, Guava's view-style utilities, the real AbstractSet.removeAll performance trap, and specialized set algebra with TreeSet ranges and EnumSet bit operations. Interviewers ask this because the code is small, but the mistakes are very common.
Below the line (out of scope)
- Set implementation internals: How HashSet, LinkedHashSet and TreeSet work
- List algorithms with duplicates preserved: List algorithms: duplicates, intersection, difference and partition
- Choosing the right collection for the workload: Choosing the right collection and Big-O cheat sheet for collections
The 30-second answer
Java set operations are usually mutating methods, so do them on a copy. Union is copy.addAll(other), intersection is copy.retainAll(other), difference is copy.removeAll(other), and symmetric difference is (a - b) union (b - a) or union.removeAll(intersection). containsAll checks subset and superset relationships; Collections.disjoint(a, b) checks whether two collections share no elements. Set.copyOf makes an unmodifiable snapshot, and streams can express the same operations with filter, contains and Collectors.toSet(). The performance catch is that AbstractSet.removeAll picks a branch by collection sizes, not by contains cost, so hashSet.removeAll(largeList) can become O(n*m) when it iterates the set and calls largeList.contains for every element. For sorted data, TreeSet range views act like set algebra over intervals; for enums, EnumSet operations are bit operations.
What you’ll learn
- Implement union, intersection, difference and symmetric difference with
addAll,retainAllandremoveAll - Avoid accidental mutation by doing set operations on copies and using
Set.copyOffor snapshots - Check subset, superset and disjoint relationships with
containsAllandCollections.disjoint - Write stream-based set operations when they improve readability, and preserve order when needed
- Explain the
AbstractSet.removeAllperformance trap with largeListarguments - Use
TreeSetrange views andEnumSetoperations as specialized set algebra
Interview radar
Asked often: How do you get union, intersection and difference of two sets in Java?
Senior rounds probe: Which calls mutate, and when can removeAll(list) be much slower than expected?
Production angle: A method returns the right answer once, then corrupts a cached set because it called retainAll on the input.
01Picture it first: Venn diagrams with side effects
Think of two sets as two circles. The math is clean, but the Java methods are more like scissors and glue: addAll, retainAll and removeAll modify the receiver. If the receiver is an input parameter, you may have changed your caller's data.
Here is the picture you should keep in your head while writing code.
Four Venn diagrams show union as both circles, intersection as the overlap, difference as the left-only region, and symmetric difference as both non-overlapping regions
The default habit is copy first, mutate the copy. That makes the method honest: callers give you sets, you return a new set, and nobody is surprised later.
02The core operations under the hood
Try it yourself first
Before reading on, write the result of each operation for paid = {u1, u2, u3} and active = {u2, u3, u4}.
import java.util.HashSet;
import java.util.Set;
public class SetAlgebra {
public static void main(String[] args) {
Set<String> paid = Set.of("u1", "u2", "u3");
Set<String> active = Set.of("u2", "u3", "u4");
Set<String> union = new HashSet<>(paid);
union.addAll(active);
Set<String> intersection = new HashSet<>(paid);
intersection.retainAll(active);
Set<String> difference = new HashSet<>(paid);
difference.removeAll(active);
Set<String> symmetricDifference = new HashSet<>(union);
symmetricDifference.removeAll(intersection);
System.out.println(union); // [u1, u2, u3, u4] in some order
System.out.println(intersection); // [u2, u3] in some order
System.out.println(difference); // [u1]
System.out.println(symmetricDifference); // [u1, u4] in some order
}
}
The operations are small because Set already gives us uniqueness. addAll adds every element from the other collection, but duplicates collapse. retainAll keeps only elements that the other collection contains. removeAll removes every element that the other collection contains.
Notice the receiver each time. We mutate union, intersection, difference and symmetricDifference, not paid or active. If you used Set.of, the inputs are unmodifiable anyway, but in production they are often mutable HashSets stored in a cache or object field.
The four operations in one table
| Operation | Meaning | Java pattern | Mutates receiver? |
|---|---|---|---|
| Union | elements in a or b |
r = new HashSet<>(a); r.addAll(b); |
yes |
| Intersection | elements in both | r = new HashSet<>(a); r.retainAll(b); |
yes |
| Difference | elements in a but not b |
r = new HashSet<>(a); r.removeAll(b); |
yes |
| Symmetric difference | elements in exactly one set | union.removeAll(intersection) |
yes |
For symmetric difference, I prefer the union-minus-intersection version in interviews because it reads like the Venn diagram. If allocation is tight, you can also build a - b, build b - a, then addAll those two sets.
static <E> Set<E> symmetricDifference(Set<E> a, Set<E> b) {
Set<E> leftOnly = new HashSet<>(a);
leftOnly.removeAll(b);
Set<E> rightOnly = new HashSet<>(b);
rightOnly.removeAll(a);
leftOnly.addAll(rightOnly);
return leftOnly;
}
03Relationship checks: subset, superset and disjoint
Some set questions do not ask you to build a new set. They ask about a relationship between two sets.
Premium lesson
Keep reading with the Java Pass
The rest of this lesson is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.
- Snapshots, streams and Guava views
- The removeAll performance trap
- Sorted and enum-specific set algebra
- Choosing the right form
- How to say it in the interview
From ₹199 · one-time payment · No auto-renewal