In this tutorial, I will demonstrate how to remove duplicate elements from an ArrayList in Java using three different approaches. Removing duplicates is a common Java coding interview question and a practical operation when working with collections.
An ArrayList allows duplicate elements. For example, if you add the same integer or string multiple times, the list stores all occurrences. However, some applications require a list containing only unique values.
Java provides several ways to remove duplicates. You can use a LinkedHashSet, the Stream API, or a loop with a Set to track elements that have already appeared.
In this tutorial, you will learn how each approach works, when to use it, and how to preserve the original order of elements. I will also explain time complexity, important edge cases, and common mistakes.
1. Problem Statement
Write a Java program to remove duplicate elements from an ArrayList while keeping only the first occurrence of each element.
The resulting list should preserve the order in which unique elements first appeared.
Example 1: Integer ArrayList
Input: [10, 20, 30, 20, 40, 10, 50, 30] Output: [10, 20, 30, 40, 50]
Explanation: The values 10, 20, and 30 appear more than once. The program keeps their first occurrences and removes the repeated values.
Example 2: String ArrayList
Input: [Java, Python, Java, Spring Boot, Python] Output: [Java, Python, Spring Boot]
The duplicate strings are removed, and the original insertion order is preserved.
2. Method 1: Remove Duplicates Using LinkedHashSet
The easiest and most practical approach is to use a LinkedHashSet.
A LinkedHashSet stores unique elements and preserves their insertion order. When you add duplicate elements to it, they do not create additional entries.
You can therefore convert an ArrayList to a LinkedHashSet and then convert the set back to an ArrayList.
Java Program Using LinkedHashSet
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
public class RemoveDuplicatesUsingSet {
public static void main(String[] args) {
ArrayList<Integer> numbers = new ArrayList<>(
Arrays.asList(10, 20, 30, 20, 40, 10, 50, 30)
);
System.out.println("Original ArrayList: " + numbers);
// Convert ArrayList to LinkedHashSet
LinkedHashSet<Integer> uniqueSet =
new LinkedHashSet<>(numbers);
// Convert LinkedHashSet back to ArrayList
ArrayList<Integer> uniqueList =
new ArrayList<>(uniqueSet);
System.out.println("ArrayList without duplicates: "
+ uniqueList);
}
}
Output
Original ArrayList: [10, 20, 30, 20, 40, 10, 50, 30] ArrayList without duplicates: [10, 20, 30, 40, 50]
Step-by-Step Explanation
Step 1: Create an ArrayList
ArrayList<Integer> numbers = new ArrayList<>(
Arrays.asList(10, 20, 30, 20, 40, 10, 50, 30)
);
This creates an ArrayList containing eight integers, including repeated values.
Step 2: Convert the ArrayList into a LinkedHashSet
LinkedHashSet<Integer> uniqueSet =
new LinkedHashSet<>(numbers);
The LinkedHashSet keeps the first occurrence of each value and ignores subsequent duplicates.
After the conversion, the set contains:
[10, 20, 30, 40, 50]
Step 3: Convert the Set Back to an ArrayList
ArrayList<Integer> uniqueList =
new ArrayList<>(uniqueSet);
This creates a new ArrayList containing only unique elements.
The original ArrayList remains unchanged.
Why Use LinkedHashSet Instead of HashSet?
Both HashSet and LinkedHashSet prevent duplicate elements. However, they differ in how they handle element order.
| Feature | HashSet | LinkedHashSet |
|---|---|---|
| Allows duplicates | No | No |
| Preserves insertion order | Not guaranteed | Yes |
| Typical add performance | O(1) average | O(1) average |
| Best use case | Unique values when order does not matter | Unique values with original order preserved |
Recommendation: Use LinkedHashSet when removing duplicates from an ArrayList and preserving the first-occurrence order is important.
3. Method 2: Remove Duplicates Using Java Streams
Java 8 introduced the Stream API, which provides a concise way to process collections.
The distinct() method removes duplicate elements from a stream.
For an ordered stream created from an ArrayList, distinct() preserves the order of the first occurrences.
Java Program Using Stream.distinct()
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class RemoveDuplicatesUsingStream {
public static void main(String[] args) {
ArrayList<Integer> numbers = new ArrayList<>(
Arrays.asList(10, 20, 30, 20, 40, 10, 50, 30)
);
System.out.println("Original ArrayList: " + numbers);
// Remove duplicates using Stream API
List<Integer> uniqueNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
System.out.println("ArrayList without duplicates: "
+ uniqueNumbers);
}
}
Output
Original ArrayList: [10, 20, 30, 20, 40, 10, 50, 30] ArrayList without duplicates: [10, 20, 30, 40, 50]
How the Stream API Solution Works
List<Integer> uniqueNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
This statement performs three operations:
stream()creates a sequential stream from the ArrayList.distinct()removes duplicate elements.collect(Collectors.toList())collects the resulting elements into a List.
Like the LinkedHashSet approach, this solution creates a new collection and does not modify the original ArrayList.
Using Stream.toList() in Java 16 and Later
If you use Java 16 or later, you can write a shorter version:
List<Integer> uniqueNumbers = numbers.stream()
.distinct()
.toList();
Important: The Stream.toList() method returns an unmodifiable List. If you need to add or remove elements afterward, create a mutable ArrayList:
ArrayList<Integer> uniqueNumbers = new ArrayList<>(
numbers.stream().distinct().toList()
);
4. Method 3: Remove Duplicates In Place Using HashSet and removeIf()
Both previous solutions create a new collection. But what if you want to remove duplicates directly from the existing ArrayList?
In that case, you can use the removeIf() method together with a HashSet.
The HashSet tracks which elements have already appeared. When the program encounters a duplicate, removeIf() removes it from the original ArrayList.
Java Program to Remove Duplicates In Place
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class RemoveDuplicatesInPlace {
public static void main(String[] args) {
ArrayList<Integer> numbers = new ArrayList<>(
Arrays.asList(10, 20, 30, 20, 40, 10, 50, 30)
);
System.out.println("Original ArrayList: " + numbers);
Set<Integer> seen = new HashSet<>();
// Remove elements that have already appeared
numbers.removeIf(number -> !seen.add(number));
System.out.println("ArrayList without duplicates: "
+ numbers);
}
}
Output
Original ArrayList: [10, 20, 30, 20, 40, 10, 50, 30] ArrayList without duplicates: [10, 20, 30, 40, 50]
Step-by-Step Explanation
First, create an empty HashSet:
Set<Integer> seen = new HashSet<>();
This set stores the elements already encountered.
Next, call:
numbers.removeIf(number -> !seen.add(number));
The Set.add() method returns:
truewhen the element is added for the first time.falsewhen the element is already present in the set.
The ! operator reverses that result.
Therefore, when an element has already appeared, the predicate returns true, and removeIf() removes that occurrence from the ArrayList.
This approach preserves the order of the remaining elements and modifies the original ArrayList.
5. Dry Run: How Duplicate Removal Works
Consider this input:
[10, 20, 10, 30, 20]
The algorithm tracks values that have already appeared.
| Step | Current Element | Already Seen? | Action | Unique Elements So Far |
|---|---|---|---|---|
| 1 | 10 | No | Keep | [10] |
| 2 | 20 | No | Keep | [10, 20] |
| 3 | 10 | Yes | Remove | [10, 20] |
| 4 | 30 | No | Keep | [10, 20, 30] |
| 5 | 20 | Yes | Remove | [10, 20, 30] |
Final result:
[10, 20, 30]
6. Removing Duplicate Strings from an ArrayList
The same techniques also work with an ArrayList of strings.
Here is an example using LinkedHashSet.
Java Program
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
public class RemoveDuplicateStrings {
public static void main(String[] args) {
ArrayList<String> languages = new ArrayList<>(
Arrays.asList(
"Java",
"Python",
"Java",
"JavaScript",
"Python",
"Spring Boot"
)
);
ArrayList<String> uniqueLanguages =
new ArrayList<>(
new LinkedHashSet<>(languages)
);
System.out.println("Original List: " + languages);
System.out.println("Unique List: " + uniqueLanguages);
}
}
Output
Original List: [Java, Python, Java, JavaScript, Python, Spring Boot] Unique List: [Java, Python, JavaScript, Spring Boot]
The LinkedHashSet removes repeated strings while preserving their original order.
By default, string comparisons are case-sensitive. Therefore, "Java" and "java" are treated as different values.
7. Time and Space Complexity
Let's compare the performance of the three approaches.
| Approach | Average Time | Auxiliary Space | Modifies Original List? |
|---|---|---|---|
| LinkedHashSet | O(n) | O(n) | No |
| Stream.distinct() | O(n) expected | O(n) | No |
| HashSet with removeIf() | O(n) expected | O(n) | Yes |
Here, n represents the number of elements in the ArrayList.
These expected time complexities assume efficient hashing. Performance may degrade when many elements have hash collisions or when equality and hash calculations are expensive.
All three approaches use additional memory to track unique elements. Even the in-place approach requires a HashSet, so it does not have constant auxiliary space complexity.
8. Important Edge Cases
Case 1: Empty ArrayList
Input: [] Output: []
All three approaches handle an empty ArrayList without errors.
Case 2: No Duplicate Elements
Input: [10, 20, 30, 40] Output: [10, 20, 30, 40]
Since every element is unique, the result remains unchanged.
Case 3: All Elements Are Duplicates
Input: [5, 5, 5, 5, 5] Output: [5]
Only the first occurrence is retained.
Case 4: Negative Numbers
Input: [-10, 20, -10, 30, 20] Output: [-10, 20, 30]
The same approach works with positive and negative integers.
Case 5: Null Elements
Input: [10, null, 20, null, 10] Output: [10, null, 20]
LinkedHashSet, HashSet, and Stream.distinct() can handle null elements. Only the first null value is retained.
9. Common Mistakes to Avoid
Mistake 1: Using HashSet When Order Matters
A regular HashSet does not guarantee insertion order. If you need to preserve the original order of elements, use LinkedHashSet.
Mistake 2: Assuming Stream.distinct() Modifies the Original List
The distinct() operation produces a stream of unique elements. It does not remove elements from the original ArrayList.
You must collect the results into a collection.
Mistake 3: Removing Elements Inside an Enhanced For Loop
Avoid removing elements directly from an ArrayList while iterating with an enhanced for loop.
For example, modifying the list structurally during iteration can cause a ConcurrentModificationException.
Use removeIf() or an iterator when you need to remove elements safely during traversal.
Mistake 4: Using Stream.toList() When a Mutable List Is Required
The Stream.toList() method returns an unmodifiable List.
If your program needs to add or remove elements afterward, wrap the result in a new ArrayList.
Mistake 5: Ignoring equals() and hashCode() for Custom Objects
When an ArrayList contains custom objects, HashSet, LinkedHashSet, and Stream.distinct() rely on equality semantics to identify duplicates.
If your custom class does not correctly implement equals() and hashCode(), logically identical objects may not be recognized as duplicates.
10. Frequently Asked Questions
Q1. What is the easiest way to remove duplicates from an ArrayList in Java?
Using LinkedHashSet is one of the easiest methods. It removes duplicate values and preserves their original insertion order.
Q2. How do I remove duplicates without changing the original ArrayList?
Create a new ArrayList from a LinkedHashSet, or collect a stream after calling distinct().
Q3. Can I remove duplicates directly from the existing ArrayList?
Yes. You can use removeIf() with a HashSet to remove repeated occurrences while preserving the first occurrence of each element.
Q4. Does HashSet preserve ArrayList order?
No. HashSet does not guarantee insertion order. Use LinkedHashSet if preserving the original order is important.
Q5. Can Stream.distinct() remove duplicate strings?
Yes. The distinct() method works with strings and other object types according to their equality behavior.
Q6. Which approach is best for Java coding interviews?
LinkedHashSet is a strong choice for a concise solution. You should also understand how to solve the problem using a HashSet and a loop, because interviewers may ask you to explain the underlying logic.
Q7. Does removing duplicates sort the ArrayList?
No. The order-preserving approaches shown in this tutorial keep the first-occurrence order rather than sorting the elements.
Q8. What happens if the ArrayList reference is null?
The examples assume a non-null ArrayList. Calling methods on a null reference results in a NullPointerException. Add a null check if your application needs to support null list references.
11. Conclusion
In this tutorial, you learned how to remove duplicate elements from an ArrayList in Java using three approaches.
- LinkedHashSet: A simple solution that removes duplicates and preserves insertion order.
- Stream.distinct(): A concise approach for removing duplicates using the Java Stream API.
- HashSet with removeIf(): Removes duplicates directly from the original ArrayList.
For most Java applications, I recommend using LinkedHashSet when you need a new ArrayList containing unique elements in their original order.
If you are already working with streams, distinct() is another excellent option. If you must modify the existing ArrayList, consider the removeIf() approach.
Understanding these techniques will also help you solve other Java collection interview problems involving sets, lists, duplicate detection, and frequency counting.
12. Official Java Documentation
For additional information about the Java collection APIs used in this tutorial, refer to the official Oracle documentation:
- Java ArrayList Class — ArrayList operations and behavior.
- Java LinkedHashSet Class — Unique elements with predictable insertion order.
- Java HashSet Class — Hash-based collection of unique elements.
- Java Stream Interface — Includes the
distinct()operation. - Java Collection Interface — Includes the
removeIf()method.
Continue Learning with Java Guides
Want to take your programming skills to the next level? Explore my practical Udemy courses on Java, Spring Boot, Microservices, Full-Stack Development, Docker, and Generative AI. You can also follow my structured learning roadmaps to become a professional developer.
By Ramesh Fadatare - Udemy Instructor Partner
Comments
Post a Comment
Leave Comment