HashSet

HashSet internals, when to use it over a List, and the equals/hashCode contract that makes or breaks it.

Last reviewed: January 2025

·Java 8Java 11Java 17Java 21
✅
Quick Understanding: HashSet is a Set backed by a HashMap. It provides O(1) add, remove, and contains operations. Use it when you need unique elements and fast membership testing. The critical requirement: elements must have correct equals() and hashCode() implementations.

🔍 What Is It?

HashSet is java.util.Set backed by a HashMap<E, PRESENT> where PRESENT is a static dummy Object.

Key Characteristics:

  • →No duplicates — add() returns false if element already present
  • →No ordering — iteration order is unpredictable
  • →O(1) average for add, remove, contains
  • →Allows one null element
  • →Not thread-safe

Relationship to HashMap:

// HashSet internally uses:
private transient HashMap<E, Object> map;
private static final Object PRESENT = new Object();

public boolean add(E e) {
    return map.put(e, PRESENT) == null;
}
public boolean contains(Object o) {
    return map.containsKey(o);
}

🎯 Why Do We Need It?

HashSet provides O(1) contains() vs O(n) for List. When you need to check "is this element already seen?" thousands of times, HashSet is the right tool. It is also the natural data structure for sets of unique identifiers, tags, permissions, and visited nodes in graph algorithms.

🏗️ What Happens in Real Projects?

Common enterprise patterns:

// Deduplication
Set<String> processedIds = new HashSet<>();
for (Event event : events) {
    if (processedIds.add(event.getId())) {  // add returns false if duplicate
        process(event);
    }
}

// Permission checking
Set<String> userRoles = user.getRoles();
if (userRoles.contains("ADMIN")) { ... }

// Visited tracking in BFS/DFS
Set<String> visited = new HashSet<>();
Queue<String> queue = new ArrayDeque<>();

⚙️ How It Works

HashSet delegates everything to its backing HashMap. add(e) calls map.put(e, PRESENT). contains(o) calls map.containsKey(o). The same hashCode/equals requirements and collision handling apply.

💻 Example

HashSet in Practice — Practical HashSet usage patterns.

java
// Basic usage
Set<String> tags = new HashSet<>();
tags.add("java");
tags.add("spring");
tags.add("java");  // duplicate — ignored, returns false
System.out.println(tags.size());  // 2

// Deduplication of a list (preserving order: use LinkedHashSet)
List<Integer> withDups = List.of(3, 1, 4, 1, 5, 9, 2, 6, 5, 3);
List<Integer> unique = new ArrayList<>(new LinkedHashSet<>(withDups));
System.out.println(unique);  // [3, 1, 4, 5, 9, 2, 6]

// Fast membership testing
Set<String> stopWords = new HashSet<>(Set.of(
    "the", "a", "an", "is", "are", "was", "were"
));
// O(1) per check — much faster than List.contains()
String[] words = text.split("\s+");
List<String> meaningful = Arrays.stream(words)
    .filter(w -> !stopWords.contains(w))
    .collect(Collectors.toList());

// Set operations
Set<Long> premiumUsers = new HashSet<>(getPremiumUserIds());
Set<Long> activeUsers = new HashSet<>(getActiveUserIds());

// Intersection: premium AND active
Set<Long> premiumActive = new HashSet<>(premiumUsers);
premiumActive.retainAll(activeUsers);

// Union: premium OR active
Set<Long> either = new HashSet<>(premiumUsers);
either.addAll(activeUsers);

// Difference: premium but NOT active (lapsed subscriptions)
Set<Long> lapsed = new HashSet<>(premiumUsers);
lapsed.removeAll(activeUsers);

🤔 Common Confusions

❓ Why is my HashSet allowing "duplicates"?

Your class does not override equals() and hashCode(). Without these, two objects with the same data are considered different (default Object identity comparison). HashSet uses equals() to check for duplicates and hashCode() to find the right bucket. Override both, or use records (Java 16+) which auto-generate them.

❓ What is the difference between HashSet and TreeSet?

HashSet: O(1) operations, no ordering, uses hashCode/equals. TreeSet: O(log n) operations, always sorted, uses compareTo/Comparator (not equals/hashCode). Use HashSet when you just need uniqueness and fast lookup. Use TreeSet when you need elements in sorted order or need range queries (headSet, tailSet, subSet).

🚨 Common Production Issues

Mutating objects stored in HashSet — changing a field used in hashCode() after adding to a HashSet makes the element unfindable. The element is in the wrong bucket.

Missing equals/hashCode — the most common HashSet bug. Always override both for custom classes.

✅ Best Practices

Always override equals() and hashCode() for objects stored in HashSet.

Use records (Java 16+) for value objects — they auto-generate equals/hashCode.

Use LinkedHashSet when you need deduplication with preserved insertion order.

Use EnumSet for sets of enum values — it is faster than HashSet.

Use Set.copyOf() to create an immutable copy of a HashSet.

🏛️ Architect's Note

Senior Architect's Perspective

In JPA/Hibernate, use Set<T> (not List<T>) for @ManyToMany and @OneToMany relationships when duplicates are not allowed. However, Hibernate has specific requirements for equals/hashCode in entities: base them on a natural business key (email, SKU) rather than the database-generated ID, because the ID is null before the entity is persisted.

💬 Frequently Asked Questions

Is HashSet ordered?

No. HashSet makes no guarantee about iteration order, and the order can change when the map resizes. If you need insertion order, use LinkedHashSet. If you need sorted order, use TreeSet. If you need a specific custom order, collect to a List and sort.

🎯 Key Takeaways

1HashSet is backed by HashMap — all HashMap characteristics apply
2O(1) average for add, remove, contains — much faster than List.contains()
3Elements must have correct equals() and hashCode()
4Never mutate objects after adding them to a HashSet
5Use LinkedHashSet for insertion-ordered uniqueness; TreeSet for sorted uniqueness
6Use EnumSet for enum values — it is the fastest possible Set implementation

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these Core Java references to verify the concept in official docs and continue into the most relevant SplashRide pages.