Introduction
Removing duplicate characters from a string is a common string manipulation problem in Java. It frequently appears in technical interviews, coding challenges, and real-world applications such as data cleaning, text normalization, and user input processing.
For example:
"hello"→"helo""banana"→"ban""programming"→"progamin"
In most scenarios, duplicate removal preserves the first occurrence of each character while maintaining the original order.
This guide explores four different approaches, from collection-based solutions to modern Java Streams, along with their advantages, limitations, and performance characteristics.
Method 1: Using LinkedHashSet (Recommended)
A LinkedHashSet automatically removes duplicates while preserving insertion order, making it one of the cleanest and most commonly recommended solutions.
import java.util.LinkedHashSet;
import java.util.Set;
public class RemoveDuplicatesLinkedHashSet {
public static String removeDuplicates(String str) {
if (str == null || str.isEmpty()) {
return "";
}
Set<Character> uniqueCharacters =
new LinkedHashSet<>();
// Store unique characters
for (char ch : str.toCharArray()) {
uniqueCharacters.add(ch);
}
StringBuilder result =
new StringBuilder();
// Build final string
for (char ch : uniqueCharacters) {
result.append(ch);
}
return result.toString();
}
public static void main(String[] args) {
System.out.println(removeDuplicates("hello"));
System.out.println(removeDuplicates("programming"));
System.out.println(removeDuplicates("banana"));
}
}
Output
helo
progamin
ban
How It Works
- Traverse every character.
- Insert it into a
LinkedHashSet. - Duplicate insertions are ignored automatically.
- Iterate over the set to reconstruct the string.
Advantages
- Preserves original order.
- Very easy to understand.
- Handles Unicode characters.
- No manual duplicate checking.
Disadvantages
- Requires additional memory.
- Slight overhead from collection operations.
Time Complexity
O(n)
Space Complexity
O(n)
Method 2: Using Stream API (Java 8+)
Java Streams provide a concise functional approach using the distinct() operation.
public class RemoveDuplicatesStream {
public static String removeDuplicates(String str) {
if (str == null || str.isEmpty()) {
return "";
}
return str.chars()
.distinct()
.collect(
StringBuilder::new,
(builder, ch) ->
builder.append((char) ch),
StringBuilder::append)
.toString();
}
public static void main(String[] args) {
System.out.println(removeDuplicates("banana"));
System.out.println(removeDuplicates("hello"));
}
}
Output
ban
helo
How It Works
chars()converts the string into anIntStream.distinct()removes duplicate character values.- The remaining characters are collected into a
StringBuilder.
Advantages
- Modern Java style.
- Very concise.
- Easy to integrate into stream pipelines.
Disadvantages
- Less intuitive for beginners.
- Slightly slower due to Stream overhead.
Time Complexity
O(n)
Space Complexity
O(n)
Method 3: Using StringBuilder with HashSet
This approach combines a HashSet for duplicate detection with a StringBuilder for efficient string construction.
import java.util.HashSet;
import java.util.Set;
public class RemoveDuplicatesHashSet {
public static String removeDuplicates(String str) {
if (str == null || str.isEmpty()) {
return "";
}
Set<Character> seen =
new HashSet<>();
StringBuilder result =
new StringBuilder();
for (char ch : str.toCharArray()) {
// add() returns true only for new characters
if (seen.add(ch)) {
result.append(ch);
}
}
return result.toString();
}
public static void main(String[] args) {
System.out.println(removeDuplicates("aabbcc"));
System.out.println(removeDuplicates("mississippi"));
}
}
Output
abc
misp
How It Works
The HashSet stores every character encountered.
If add() returns:
true→ character is new → append it.false→ duplicate → skip it.
Advantages
- Fast duplicate detection.
- Preserves first occurrence.
- Efficient for large strings.
Disadvantages
- Requires two data structures.
- Slightly more code than the
LinkedHashSetapproach.
Time Complexity
O(n)
Space Complexity
O(n)
Method 4: Using Regular Expression
Regular expressions can remove duplicate characters using a backreference.
public class RemoveDuplicatesRegex {
public static String removeDuplicates(String str) {
if (str == null || str.isEmpty()) {
return "";
}
return str.replaceAll("(.)(?=.*\\1)", "");
}
public static void main(String[] args) {
System.out.println(removeDuplicates("hello"));
System.out.println(removeDuplicates("banana"));
}
}
Output
helo
ban
Understanding the Regex
(.)
Captures one character.
(?=.*\1)
Checks whether the same character appears later in the string.
If it does, the current occurrence is removed.
Advantages
- Very compact implementation.
- Good demonstration of regex features.
Disadvantages
- Hard to understand.
- Slower than loop-based approaches.
- Not recommended for beginners or performance-critical applications.
Time Complexity
Approximately O(n²) in many practical cases because of regex backtracking.
Space Complexity
Depends on regex engine implementation.
Removing Duplicates While Ignoring Case
Sometimes "A" and "a" should be treated as the same character.
public static String removeDuplicatesIgnoreCase(String str) {
if (str == null || str.isEmpty()) {
return "";
}
Set<Character> seen =
new HashSet<>();
StringBuilder result =
new StringBuilder();
for (char ch : str.toCharArray()) {
char lower =
Character.toLowerCase(ch);
if (seen.add(lower)) {
result.append(ch);
}
}
return result.toString();
}
Example:
Input:
JavaJAVA
Output:
Jav
Removing Duplicates While Ignoring Spaces
for (char ch : str.toCharArray()) {
if (Character.isWhitespace(ch)) {
continue;
}
if (seen.add(ch)) {
result.append(ch);
}
}
Keeping the Last Occurrence Instead of the First
Traverse the string from right to left.
StringBuilder result =
new StringBuilder();
Set<Character> seen =
new HashSet<>();
for (int i = str.length() - 1; i >= 0; i--) {
char ch = str.charAt(i);
if (seen.add(ch)) {
result.append(ch);
}
}
System.out.println(result.reverse());
Example:
Input:
banana
Output:
bna
Performance Comparison
| Method | Time Complexity | Space Complexity | Preserves Order | Readability |
|---|---|---|---|---|
| LinkedHashSet | O(n) | O(n) | ✔ | Excellent |
| Streams | O(n) | O(n) | ✔ | Good |
| HashSet + StringBuilder | O(n) | O(n) | ✔ | Excellent |
| Regular Expression | O(n²) (practical) | Depends | ✔ | Moderate |
Best Practices
Validate Input
if (str == null || str.isEmpty()) {
return "";
}
Preserve Order When Needed
Use LinkedHashSet instead of HashSet.
Set<Character> seen =
new LinkedHashSet<>();
Ignore Case if Required
str = str.toLowerCase();
Avoid Regex for Large Strings
Regex is elegant but considerably slower than loop-based approaches.
Prefer StringBuilder
Avoid repeatedly concatenating strings.
StringBuilder builder =
new StringBuilder();
Frequently Asked Questions
Q1: Does order matter?
Answer: Usually yes. Most interview questions expect the original order to be preserved. LinkedHashSet is the simplest solution for this requirement.
Q2: Which method is fastest?
Answer: LinkedHashSet and HashSet with StringBuilder both run in O(n) time and are generally the fastest practical solutions.
Q3: How do I ignore case?
Answer: Convert the string to lowercase (or uppercase) before processing.
str = str.toLowerCase();
Q4: What about Unicode characters?
Answer: LinkedHashSet, HashSet, and Streams work correctly with Unicode characters because they operate on Java char values.
Q5: Can I remove duplicates but keep the last occurrence?
Answer: Yes. Traverse the string from the end, store unseen characters, then reverse the result.
Q6: How do I remove only consecutive duplicates?
Answer: Compare each character with the previous one.
if (i == 0 || str.charAt(i) != str.charAt(i - 1)) {
result.append(str.charAt(i));
}
Example:
Input:
aaabbbcccaaa
Output:
abca
Q7: How do these methods perform with large strings?
Answer: Loop-based methods scale linearly (O(n)). Regex-based solutions become noticeably slower on large inputs.
Q8: Can I exclude digits or spaces?
Answer: Yes. Add a filtering condition before processing.
if (!Character.isLetter(ch)) {
continue;
}
Q9: How do I count removed duplicates?
Answer: Maintain a separate counter whenever a duplicate is detected.
if (!seen.add(ch)) {
duplicateCount++;
}
Q10: Do these methods work with special characters?
Answer: Yes. All methods treat punctuation and symbols as normal characters unless you explicitly filter them out.