Introduction
Finding duplicate characters in a string is a common Java programming problem that appears in technical interviews, coding assignments, and text-processing applications. It helps you understand character frequency analysis, collections, hashing, and efficient string traversal.
For example:
"hello"→ Duplicate character: l"programming"→ Duplicate characters: r, g, m"mississippi"→ Duplicate characters: i, s, p
This guide explores four different approaches, from beginner-friendly solutions to modern Java Stream API implementations.
Method 1: Using HashMap (Character Frequency)
The most common approach is to store the frequency of each character using a HashMap. Any character whose count is greater than one is considered a duplicate.
import java.util.HashMap;
import java.util.Map;
public class DuplicateCharactersHashMap {
public static void findDuplicates(String str) {
if (str == null || str.isEmpty()) {
System.out.println("Input string is empty.");
return;
}
Map<Character, Integer> characterCount =
new HashMap<>();
// Count frequency of each character
for (char ch : str.toCharArray()) {
characterCount.put(
ch,
characterCount.getOrDefault(ch, 0) + 1
);
}
System.out.println("Duplicate characters:");
boolean found = false;
for (Map.Entry<Character, Integer> entry :
characterCount.entrySet()) {
if (entry.getValue() > 1) {
found = true;
System.out.println(
entry.getKey() + " : " + entry.getValue());
}
}
if (!found) {
System.out.println("No duplicate characters found.");
}
}
public static void main(String[] args) {
findDuplicates("hello");
}
}
Output
Duplicate characters:
l : 2
How It Works
- Traverse every character.
- Store its frequency inside a
HashMap. - Print entries whose frequency is greater than one.
Advantages
- Works with Unicode characters.
- Easy to understand.
- Provides duplicate counts.
- Suitable for most real-world applications.
Disadvantages
- Requires extra memory for the map.
Time Complexity
O(n)
Space Complexity
O(k)
Where k is the number of unique characters.
Method 2: Using HashSet
A HashSet can quickly determine whether a character has already been seen.
import java.util.HashSet;
import java.util.Set;
public class DuplicateCharactersHashSet {
public static void findDuplicates(String str) {
if (str == null || str.isEmpty()) {
System.out.println("Input string is empty.");
return;
}
Set<Character> seen =
new HashSet<>();
Set<Character> duplicates =
new HashSet<>();
for (char ch : str.toCharArray()) {
if (!seen.add(ch)) {
duplicates.add(ch);
}
}
System.out.println(
"Duplicate characters: " + duplicates);
}
public static void main(String[] args) {
findDuplicates("programming");
}
}
Output
Duplicate characters: [r, g, m]
The order may vary because
HashSetdoes not preserve insertion order.
Advantages
- Very concise.
- Efficient duplicate detection.
- No frequency counting required.
Disadvantages
- Doesn't provide occurrence counts.
- Output order is unpredictable.
Maintaining Order
If you want duplicates in the same order they appear, use LinkedHashSet.
Set<Character> duplicates =
new LinkedHashSet<>();
Time Complexity
O(n)
Space Complexity
O(k)
Method 3: Using Character Array (ASCII)
If the input contains only ASCII characters, an integer array provides the fastest solution.
public class DuplicateCharactersArray {
public static void findDuplicates(String str) {
if (str == null || str.isEmpty()) {
System.out.println("Input string is empty.");
return;
}
int[] count = new int[256];
for (char ch : str.toCharArray()) {
if (ch < 256) {
count[ch]++;
}
}
System.out.println("Duplicate characters:");
boolean found = false;
for (int i = 0; i < count.length; i++) {
if (count[i] > 1) {
found = true;
System.out.println(
(char) i + " : " + count[i]);
}
}
if (!found) {
System.out.println("No duplicate characters found.");
}
}
public static void main(String[] args) {
findDuplicates("mississippi");
}
}
Output
Duplicate characters:
i : 4
p : 2
s : 4
How It Works
Each character's ASCII value becomes the index of the array.
Example:
count['a']++
count['b']++
count['c']++
Advantages
- Extremely fast.
- Constant-time indexing.
- Excellent for interview questions involving ASCII.
Disadvantages
- Limited to ASCII.
- Not suitable for Unicode characters.
Time Complexity
O(n)
Space Complexity
O(1)
The array size remains fixed at 256.
Method 4: Using Stream API
Modern Java (Java 8+) allows duplicate detection using Streams.
import java.util.stream.Collectors;
public class DuplicateCharactersStream {
public static void main(String[] args) {
String str = "java";
str.chars()
.boxed()
.collect(Collectors.groupingBy(
c -> (char) c.intValue(),
Collectors.counting()))
.entrySet()
.stream()
.filter(entry -> entry.getValue() > 1)
.forEach(entry ->
System.out.println(
entry.getKey() +
" : " +
entry.getValue()));
}
}
Output
a : 2
Advantages
- Modern functional programming style.
- Compact implementation.
- Easy to integrate with stream pipelines.
Disadvantages
- Less beginner-friendly.
- Higher overhead than loops.
- Harder to debug.
Time Complexity
O(n)
Space Complexity
O(k)
Handling Common Variations
Ignore Case
Convert the string to lowercase before processing.
str = str.toLowerCase();
Example:
"JavaJAVA"
↓
"javajava"
Duplicates:
j : 2
a : 4
v : 2
Ignore Spaces
str = str.replaceAll("\\s+", "");
Ignore Non-Letter Characters
for (char ch : str.toCharArray()) {
if (!Character.isLetter(ch)) {
continue;
}
// Count letters only
}
Performance Comparison
| Method | Time Complexity | Space Complexity | Unicode Support | Frequency Count |
|---|---|---|---|---|
| HashMap | O(n) | O(k) | ✔ | ✔ |
| HashSet | O(n) | O(k) | ✔ | ✘ |
| Character Array | O(n) | O(1) | ✘ (ASCII only) | ✔ |
| Streams | O(n) | O(k) | ✔ | ✔ |
Best Practices
Validate Input
if (str == null || str.isEmpty()) {
return;
}
Ignore Case When Required
str = str.toLowerCase();
Use LinkedHashMap to Preserve Order
Map<Character, Integer> frequency =
new LinkedHashMap<>();
This prints duplicates in the order they first appear.
Choose the Right Data Structure
- ASCII input → Character array
- Unicode input → HashMap
- Need insertion order → LinkedHashMap
- Need only duplicates → HashSet
Frequently Asked Questions
Q1: Should I count spaces as characters?
Answer: Yes, by default every character is counted. Skip whitespace using:
if (Character.isWhitespace(ch)) {
continue;
}
Q2: Which method is fastest?
Answer: The character array approach is the fastest for ASCII input. For Unicode strings, HashMap offers the best balance between speed and flexibility.
Q3: How do I ignore case?
Answer: Convert the string to lowercase (or uppercase) before counting.
str = str.toLowerCase();
Q4: How do I find the first duplicate character?
Answer: Traverse the string while maintaining a HashSet. The first character that cannot be added to the set is the first duplicate.
Set<Character> seen = new HashSet<>();
for (char ch : str.toCharArray()) {
if (!seen.add(ch)) {
System.out.println(ch);
break;
}
}
Q5: What about special characters?
Answer: All approaches treat special characters as normal characters unless you explicitly filter them out.
Q6: How do I print duplicates in insertion order?
Answer: Use LinkedHashMap or LinkedHashSet instead of their regular counterparts.
Q7: Can these methods handle Unicode?
Answer: Yes. HashMap, HashSet, and Streams work correctly with Unicode characters. The character-array approach is suitable only for ASCII.
Q8: How do these methods perform on very large strings?
Answer: All loop-based methods run in O(n) time. The character-array method is the fastest for ASCII, while HashMap scales well for Unicode.
Q9: How do I exclude digits or punctuation?
Answer: Add a condition before counting.
if (!Character.isLetter(ch)) {
continue;
}
Q10: How do I ignore whitespace characters?
Answer: Skip them during iteration.
if (Character.isWhitespace(ch)) {
continue;
}