Remove Duplicates Using Streams
Java coding interview problem for Java 8 Streams: Remove Duplicates Using Streams.
Removing duplicate elements is one of the most common Java Collections interview problems.
This problem helps understand:
- Stream API
- Set behavior
distinct()operation- Object equality
equals()andhashCode()- Data cleaning patterns
What are Duplicate Elements?
A duplicate element is a value that appears more than once in a collection.
Example:
Input:
[10,20,30,20,40,10]
Frequency:
10 → 2 times
20 → 2 times
30 → 1 time
40 → 1 time
After removing duplicates:
[10,20,30,40]
Difference Between Removing Duplicates and Finding Duplicates
These two problems look similar but have different goals.
Finding Duplicates
Question:
Which elements appear more than once?
Example:
Input:
[1,2,3,2,4,1]
Output:
[1,2]
Removing Duplicates
Question:
Keep only unique elements.
Example:
Input:
[1,2,3,2,4,1]
Output:
[1,2,3,4]
Understanding Unique Elements
A unique collection means:
Every value appears only once
Example:
Before:
Java
Spring
Java
AWS
After:
Java
Spring
AWS
Why Duplicate Removal is Important?
Duplicate removal is a common operation in enterprise applications.
Data Processing
Clean:
- Customer records
- Transaction data
- Logs
Database Migration
Remove:
- Duplicate rows
- Duplicate identifiers
Search Systems
Avoid:
- Duplicate results
- Repeated recommendations
Analytics
Generate accurate:
- Reports
- Metrics
- Statistics
Real-World Applications
Customer Management
Input:
Email list
Remove:
Duplicate emails
E-Commerce
Remove duplicate:
- Product IDs
- Orders
- Recommendations
Security Systems
Remove duplicate:
- User sessions
- Events
Problem Statement
Given a list of elements, remove duplicate values using Java Streams.
Example 1
Input:
[1,2,3,2,4,1,5]
Output:
[1,2,3,4,5]
Example 2
Input:
["Java","Spring","Java","AWS"]
Output:
["Java","Spring","AWS"]
Java Stream API Overview
Java Stream API provides a functional way to process collections.
Stream flow:
Collection
↓
Stream
↓
Operations
↓
Result
Example:
list.stream()
.distinct()
.toList();
Stream Pipeline Concept
A Stream pipeline contains:
Source
↓
Intermediate Operations
↓
Terminal Operation
Example:
numbers.stream()
.distinct()
.collect()
Flow:
List
↓
stream()
↓
distinct()
↓
collect()
↓
Unique List
Approach 1 — Traditional HashSet Approach
Before Streams, developers commonly used Set.
Algorithm
- Create an empty Set.
- Traverse the list.
- Add elements to Set.
- Set automatically removes duplicates.
Java Program
import java.util.*;
public class RemoveDuplicatesUsingSet {
public static List<Integer> removeDuplicates(
List<Integer> numbers) {
Set<Integer> unique =
new LinkedHashSet<>();
unique.addAll(numbers);
return new ArrayList<>(
unique
);
}
public static void main(String[] args) {
List<Integer> numbers =
Arrays.asList(
1,
2,
3,
2,
4,
1
);
System.out.println(
removeDuplicates(numbers)
);
}
}
Output
[1,2,3,4]
Step-by-Step Explanation
Input:
[1,2,3,2,4,1]
Initial Set:
[]
Add:
1
Set:
[1]
Add:
2
Set:
[1,2]
Add:
3
Set:
[1,2,3]
Add duplicate:
2
Already exists.
Ignored.
Add:
4
Set:
[1,2,3,4]
Add duplicate:
1
Ignored.
Final:
[1,2,3,4]
Why LinkedHashSet?
There are different Set implementations.
HashSet
Provides:
No ordering guarantee
LinkedHashSet
Maintains:
Insertion order
Example:
Input:
Java
Spring
Java
AWS
LinkedHashSet output:
Java
Spring
AWS
Approach 2 — Using Stream distinct()
Java Stream provides:
distinct()
for removing duplicates.
Syntax
stream.distinct()
Example:
numbers.stream()
.distinct()
.toList();
Java Program — Using distinct()
import java.util.*;
public class RemoveDuplicatesUsingStreams {
public static List<Integer> removeDuplicates(
List<Integer> numbers) {
return numbers.stream()
.distinct()
.toList();
}
public static void main(String[] args) {
List<Integer> numbers =
Arrays.asList(
10,
20,
30,
20,
40,
10
);
System.out.println(
removeDuplicates(numbers)
);
}
}
Output
[10,20,30,40]
Dry Run — distinct()
Input:
[10,20,30,20,40,10]
First element:
10
Store:
[10]
Second:
20
Store:
[10,20]
Third:
30
Store:
[10,20,30]
Fourth:
20
Already seen.
Skip.
Fifth:
40
Add:
[10,20,30,40]
Sixth:
10
Skip.
Result:
[10,20,30,40]
Removing Duplicate Strings
Example:
Input:
[
"Java",
"Spring",
"Java",
"AWS"
]
Code:
List<String> unique =
languages.stream()
.distinct()
.toList();
Output:
[
Java,
Spring,
AWS
]
Removing Duplicate Objects
For primitive values:
distinct()
works directly.
For custom objects:
Java needs:
equals()
and
hashCode()
Example:
Employee:
id
name
department
Duplicate rule:
Same employee id
Employee Class
class Employee {
private int id;
private String name;
public Employee(
int id,
String name) {
this.id = id;
this.name = name;
}
public int getId(){
return id;
}
@Override
public boolean equals(
Object obj) {
Employee employee =
(Employee)obj;
return this.id ==
employee.id;
}
@Override
public int hashCode(){
return id;
}
}
Remove Duplicate Employees
List<Employee> uniqueEmployees =
employees.stream()
.distinct()
.toList();
Why equals() and hashCode()?
Stream distinct() internally uses Set behavior.
Set determines duplicates using:
hashCode()
+
equals()
Without overriding:
Two objects with same data
may be considered different
Preserving Order While Removing Duplicates
Example:
Input:
[5,3,5,2,3]
Expected:
[5,3,2]
Stream:
numbers.stream()
.distinct()
.toList();
Output preserves:
Encounter order
Complexity Analysis
Let:
n = number of elements
Traversal:
O(n)
Hash lookup:
Average:
O(1)
Total Time:
O(n)
Space:
O(n)
because unique values are stored internally.
Advantages
- Simple syntax.
- Preserves encounter order.
- Works with Streams.
- Good readability.
- Handles objects with proper equality.
Drawbacks
- Requires memory for tracking values.
- Object deduplication requires equals/hashCode.
- Not suitable when custom duplicate rules are needed.
Deep Dive Into Stream distinct()
The Stream API method:
distinct()
removes duplicate elements from a stream.
How distinct() Works Internally
distinct() internally maintains a Set of already seen elements.
Conceptually:
Stream Elements
↓
Check HashSet
↓
Already Exists?
↓
Yes → Ignore
No → Keep
Example:
Input:
[10,20,10,30,20]
Internal tracking:
Seen Set:
{}
Process:
10 → Add
20 → Add
10 → Already exists
30 → Add
20 → Already exists
Output:
[10,20,30]
Internal Data Structure
For sequential streams:
HashSet
is used internally.
The duplicate check uses:
hashCode()
+
equals()
Remove Duplicate Objects Using Streams
Consider:
Employee objects:
Employee(101,"John")
Employee(102,"Alice")
Employee(101,"John")
Requirement:
Remove duplicate employees based on:
Employee ID
Employee Class
class Employee {
private int id;
private String name;
public Employee(
int id,
String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
@Override
public String toString() {
return id + " " + name;
}
}
Problem
If we directly use:
employees.stream()
.distinct()
it compares complete objects.
We need custom duplicate logic:
Same ID = Duplicate
Approach 1 — Using Set With filter()
Create a Set to track IDs.
Set<Integer> employeeIds =
new HashSet<>();
List<Employee> uniqueEmployees =
employees.stream()
.filter(
employee ->
employeeIds.add(
employee.getId()
)
)
.toList();
Dry Run
Input:
101 John
102 Alice
101 Bob
Set:
{}
First:
101
Add:
{101}
Keep.
Second:
102
Add:
{101,102}
Keep.
Third:
101
Already exists.
Remove.
Output:
101 John
102 Alice
Approach 2 — Using Collectors.toMap()
Another powerful approach:
Use a Map where:
Key = Unique Identifier
Example:
Employee ID:
101
Code:
Map<Integer,Employee> employeeMap =
employees.stream()
.collect(
Collectors.toMap(
Employee::getId,
Function.identity(),
(existing,replacement)
-> existing
)
);
List<Employee> uniqueEmployees =
new ArrayList<>(
employeeMap.values()
);
Understanding Merge Function
When duplicate key appears:
(existing,replacement)
-> existing
means:
Keep the first object.
Example:
101 John
101 Bob
Result:
101 John
To keep latest object:
(existing,replacement)
-> replacement
Result:
101 Bob
Remove Duplicate Employees by Name
Requirement:
Same name = duplicate
Use:
Set<String> names =
new HashSet<>();
List<Employee> result =
employees.stream()
.filter(
employee ->
names.add(
employee.getName()
)
)
.toList();
Remove Duplicate Employees by Department
Example:
Employees:
John IT
Alice HR
Bob IT
Requirement:
One employee per department.
Code:
Set<String> departments =
new HashSet<>();
List<Employee> result =
employees.stream()
.filter(
employee ->
departments.add(
employee.getDepartment()
)
)
.toList();
Output:
John IT
Alice HR
TreeSet Approach for Sorted Unique Values
Sometimes requirement:
Remove duplicates
+
Sort values
Example:
Input:
[5,2,8,2,1,5]
Expected:
[1,2,5,8]
Use:
List<Integer> result =
numbers.stream()
.collect(
Collectors.toCollection(
TreeSet::new
)
)
.stream()
.toList();
TreeSet Internals
TreeSet uses:
Red-Black Tree
Provides:
- Unique elements
- Sorted order
Complexity:
Insertion:
O(log n)
Custom Key Extraction Approach
A reusable pattern:
static <T> Predicate<T> distinctByKey(
Function<T,?> keyExtractor) {
Set<Object> seen =
ConcurrentHashMap
.newKeySet();
return item ->
seen.add(
keyExtractor.apply(item)
);
}
Usage:
employees.stream()
.filter(
distinctByKey(
Employee::getId
)
)
.toList();
This removes duplicates using:
Any custom field
Stream vs Loop Comparison
| Feature | Loop + Set | Stream distinct() |
|---|---|---|
| Code | More | Less |
| Readability | Good | Excellent |
| Custom rules | Easy | Needs customization |
| Performance | Very similar | Very similar |
| Functional style | No | Yes |
HashSet Internal Working
Duplicate removal depends on HashSet behavior.
When adding:
set.add(value)
Java performs:
Value
↓
hashCode()
↓
Bucket Location
↓
equals()
↓
Duplicate Check
Example:
Two employees:
Employee(101)
Employee(101)
If:
hashCode same
+
equals true
then:
Duplicate
Handling Null Values
Input:
[10,20,null,10]
Stream:
numbers.stream()
.filter(
Objects::nonNull
)
.distinct()
.toList();
Output:
[10,20]
Keeping Null Value
distinct() supports null.
Example:
[10,20,null,10,null]
Output:
[10,20,null]
Parallel Stream Considerations
Example:
parallelStream()
.distinct()
Java maintains correctness.
However:
Parallel processing requires:
- Additional coordination
- More memory
- Thread management
For small collections:
Prefer:
stream()
For very large data:
Evaluate:
parallelStream()
Common Interview Mistakes
Mistake 1
Using:
distinct()
for custom object fields.
Problem:
Two objects may look same but have different references.
Solution:
Use:
equals/hashCode
or
Set with key extractor
Mistake 2
Using HashSet when order matters.
Example:
Need:
Original order
Use:
LinkedHashSet
Mistake 3
Ignoring duplicate business rules.
Ask:
Duplicate based on what field?
Examples:
- ID
- Name
- Department
Mistake 4
Using TreeSet without Comparator.
Objects need:
Comparable
or
Comparator
Edge Cases
| Case | Handling |
|---|---|
| Empty list | Return empty list |
| Single element | Already unique |
| All duplicates | Return one value |
| Null values | Filter or handle |
| Objects | Define equality |
Interview Follow-up Questions
Q1. How does Stream distinct() work internally?
Q2. Difference between HashSet and LinkedHashSet?
Q3. Remove duplicate objects by field.
Q4. Remove duplicates and maintain order.
Q5. Remove duplicates and sort values.
Q6. Difference between distinct() and groupingBy()?
Q7. How does HashSet identify duplicates?
Related Java Collection Problems
- Find Duplicate Elements Using Streams
- Character Frequency Using Streams
- Count Word Frequency Using HashMap
- Convert List to Map
- Group Employees by Department
- Find Intersection of Two Lists
- Custom Comparator Examples
Key Takeaways
Duplicate removal patterns:
Primitive Values
↓
stream()
↓
distinct()
Custom Objects:
Object List
↓
Unique Key
↓
Set / Map
↓
Deduplicated Result
Recommended approaches:
Simple values
Use:
distinct()
Custom object fields
Use:
filter()
+
Set
or:
Collectors.toMap()
Sorted unique values
Use:
TreeSet
Complexity:
HashSet approach:
Time: O(n)
Space: O(n)
TreeSet approach:
Time: O(n log n)
Space: O(n)
Frequently Asked Interview Questions
Q1. Does distinct() preserve order?
Yes, for ordered streams.
Q2. What does distinct() use internally?
A Set-based mechanism using hashCode and equals.
Q3. How to remove duplicates based on a field?
Use a Set containing the extracted field value.
Q4. How to keep the latest duplicate object?
Use:
Collectors.toMap()
with replacement merge function.
Interview Tip
When asked:
"Remove duplicates using Java Streams."
Explain:
- For primitive values use
distinct(). - For objects define equality or use key-based filtering.
- Use
LinkedHashSetwhen order matters. - Use
TreeSetwhen sorted unique output is required. - Discuss HashSet internals and complexity.
For senior Java interviews, discuss:
- Stream internals.
- HashSet behavior.
- Object equality contracts.
- Collector strategies.
- Performance trade-offs.
This demonstrates strong understanding of Java Streams, Collections, and real-world data deduplication techniques.