How HashSet, LinkedHashSet and TreeSet work
HashSet, LinkedHashSet and TreeSet internals, ordering, equality contracts, null handling, navigable operations, EnumSet and complexity trade-offs.
On this page
🧺 What are HashSet, LinkedHashSet and TreeSet?
A Set is a collection that keeps unique elements. HashSet is the everyday membership set, LinkedHashSet keeps unique elements in encounter order, and TreeSet keeps them sorted. If product names arrive as ["mouse", "keyboard", "mouse"], a set stores two names, not three.
For this lesson, we'll focus on how the three main set implementations work in Java 21, why their equality rules differ, and which one I would choose in common interview scenarios: unique product names, sign-up order for unique user IDs, sorted unique student IDs, and enum roles. Interviewers ask this because sets look small from the API, but they reveal whether you understand maps, equality, ordering and mutation.
Below the line (out of scope)
- Hash table resizing and tree bins in full depth: How HashMap works and Hashing, collisions, treeification and load factor
- Red-black tree mechanics in full depth: TreeMap and red-black trees
- Comparator design: Comparable vs Comparator
- Set algebra (
union,intersection,difference): Set operations: union, intersection and difference
The 30-second answer
HashSet is backed by a HashMap<E,Object>: the set element is stored as the map key and every key points to the same dummy PRESENT object. add(e) is essentially map.put(e, PRESENT) == null, so it returns true only when the key was absent. LinkedHashSet uses a LinkedHashMap, so it has hash-table lookup plus insertion-order iteration and, since Java 21, SequencedSet methods like getFirst, getLast, addFirst and reversed(). TreeSet uses a TreeMap, so elements are ordered by Comparable or a Comparator, with O(log n) add, remove, contains and nearest-match operations like floor and ceiling. HashSet and LinkedHashSet allow one null; TreeSet with natural ordering rejects null; EnumSet is the best set for enum constants because it is a bit vector.
What you’ll learn
- Explain why a
HashSetis a **HashMapwith dummy values** and whatadd()returns - Use the **
equals/hashCodecontract** safely and avoid the mutable-key bug - Choose
HashSet,LinkedHashSet,TreeSetorEnumSetbased on order and lookup cost - Describe Java 21
SequencedSetmethods onLinkedHashSet, includingaddFirst,getLastandreversed() - Use
TreeSetwithComparableorComparator, and spot comparators that are inconsistent with equals - Apply
NavigableSetmethods likefloor,ceiling,subSet,descendingSetandpollFirst
Interview radar
Asked very often: How does HashSet detect duplicates, and why does mutating a stored object break lookup?
Senior rounds probe: Why can a TreeSet drop two unequal objects when the comparator returns 0?
Production angle: A deduplication job starts emitting duplicates or leaking memory because mutable IDs were inserted into a hash-based set.
01Picture it first: three guest lists
Think of sets as three guest lists at an event. HashSet is the bouncer's stamp book: it answers "have I seen this person?" without caring about the line order. LinkedHashSet is the same stamp book plus a clipboard that records the first arrival order. TreeSet is a sorted register that keeps names in alphabetical or numeric order as people arrive.
The first picture shows the key trick behind HashSet: it is a map wearing a set-shaped jacket.
A HashSet stores each element as a key in an internal HashMap, and every key points to the same dummy PRESENT value
Now compare iteration order. Add the same five numbers to each set and ask what a for loop sees.
HashSet, LinkedHashSet and TreeSet receive the same inserted values, then iterate in unspecified hash order, insertion order and sorted order
Notice three things:
HashSetoptimizes membership, not human-readable order.LinkedHashSetgives deterministic insertion order without sorting.TreeSetgives sorted order and range queries, but pays O(log n) per update.
02HashSet under the hood
Try it yourself first
Before reading on, predict the three printed values. Why does the second add return false?
import java.util.HashSet;
import java.util.Set;
public class UniqueProductNames {
public static void main(String[] args) {
Set<String> names = new HashSet<>();
System.out.println(names.add("keyboard"));
System.out.println(names.add("mouse"));
System.out.println(names.add("keyboard"));
System.out.println(names);
}
}
The first two calls print true; the third prints false. The set already contains an equal key, so the structure is unchanged. The final order is deliberately not promised, so do not write a test that expects [keyboard, mouse].
OpenJDK's HashSet has this shape:
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT) == null;
}
That line is the whole idea. A HashMap already knows how to store unique keys, spread hashes, handle collisions, resize and treeify crowded bins. HashSet reuses all of that and ignores the map value.
Notice the interaction:
contains(e)delegates tomap.containsKey(e).remove(e)removes the map key and returns whether the dummy value was present.- The element's
hashCode()picks the bucket, thenequals()confirms the match inside the bucket. - Expected add, remove and contains are O(1); under severe collisions, Java 8+ tree bins can make a crowded bucket O(log n) once the table is large enough.
This is why the usual product-name question is a HashSet question. If the requirement is "store unique product names and test membership", HashSet<String> is the default. If the requirement also says "print them in the order first seen", use LinkedHashSet<String> instead.
The equals and hashCode contract
Hash-based sets depend on one contract: equal objects must have equal hash codes. Unequal objects may collide, but equal objects cannot be sent to different buckets. If equals() says two users are the same email but hashCode() uses a database id, your set will behave like it is haunted.
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
final class ProductId {
private final String value;
ProductId(String value) {
this.value = value;
}
@Override public boolean equals(Object other) {
return other instanceof ProductId id && Objects.equals(value, id.value);
}
@Override public int hashCode() {
return Objects.hash(value);
}
}
public class ProductIdSet {
public static void main(String[] args) {
Set<ProductId> seen = new HashSet<>();
seen.add(new ProductId("SKU-7"));
System.out.println(seen.contains(new ProductId("SKU-7"))); // true
}
}
Records are often the cleanest way to make set elements because they generate consistent equals() and hashCode() from final components. We cover the contract more deeply in The equals() and hashCode() contracts.
The mutable-key bug
Try it yourself first
Read the next program without running it. Why can a set of size 1 say it does not contain the exact object you inserted?
Premium lesson
Keep reading with the Java Pass
The rest of this lesson is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.
- LinkedHashSet: uniqueness plus insertion order
- TreeSet: sorted uniqueness and navigation
- Nulls, EnumSet and the decision table
- How to say it in the interview
From ₹199 · one-time payment · No auto-renewal