Java sets
Java's Set implementations: HashSet, LinkedHashSet and TreeSet, how HashSet is built on HashMap, and the difference between ordered, sorted and unordered.
Learning goals
- Choose the right Set for membership, insertion order or sorted order
- Explain that HashSet delegates to HashMap
- Use add's return value and the sorted-set navigation methods
Prerequisites
HashSet
HashSet<E> stores each element once and answers contains in expected O(1). add returns true if the element was new and false if it was already present, which makes duplicate detection a one-liner. Iteration order is unspecified.
Elements need consistent hashCode and equals, exactly like map keys.
1static boolean hasDuplicate(int[] a) {2Set<Integer> seen = new HashSet<>();3for (int x : a) if (!seen.add(x)) return true; // add returns false when x was already there4return false;5}
LinkedHashSet
LinkedHashSet is a HashSet that also threads a linked list through its elements, so iteration follows insertion order. Re-adding an element that is already present does not move it. Use it to remove duplicates while keeping the first-seen order, or when output must be reproducible.
1static List<Integer> dedupe(int[] a) {2Set<Integer> seen = new LinkedHashSet<>();3for (int x : a) seen.add(x);4return new ArrayList<>(seen); // first-seen order: [42, 7, 19, 3, 25, 16, 33] for the animation's input5}
TreeSet
TreeSet keeps its elements sorted in a red-black tree: add, contains and remove are O(log n). It adds order-based queries: first, last, floor(x) (largest element ≤ x), ceiling(x) (smallest ≥ x), headSet, tailSet and subSet.
A TreeSet decides two elements are the same when its comparator returns 0, not when equals says so. With String.CASE_INSENSITIVE_ORDER, adding "a" and then "A" leaves a one-element set [a].
1static void sortedSet() {2TreeSet<Integer> s = new TreeSet<>(List.of(10, 20, 30));3System.out.println(s.floor(25) + " " + s.ceiling(25) + " " + s.first() + " " + s.headSet(20)); // 20 30 10 [10]4TreeSet<String> ci = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);5ci.add("a"); ci.add("A");6System.out.println(ci); // [a]7}
HashSet backed by HashMap
HashSet is a thin wrapper around a HashMap: every element is a key and every value is the same dummy object. add(e) is map.put(e, PRESENT) == null, contains(e) is map.containsKey(e) and remove(e) is map.remove(e) == PRESENT. LinkedHashSet wraps a LinkedHashMap and TreeSet wraps a TreeMap in the same way.
This explains why a set has the same costs, the same iteration order and the same hashCode/equals requirements as the corresponding map, and why a set uses more memory than the stored elements alone suggest.
1final class MiniSet<E> {2private static final Object PRESENT = new Object();3private final HashMap<E, Object> map = new HashMap<>();4boolean add(E e) { return map.put(e, PRESENT) == null; } // null means the key was new5boolean contains(Object e) { return map.containsKey(e); }6boolean remove(Object e) { return map.remove(e) == PRESENT; }7int size() { return map.size(); }8}
Ordered vs sorted vs unordered
Three words are often confused. **Unordered**: no promise about iteration order (HashSet). **Ordered** (insertion-ordered): iteration follows the order in which elements were added (LinkedHashSet, ArrayList). **Sorted**: iteration follows a comparison order of the values themselves (TreeSet).
Run the animation and compare the three outputs for the same input values. Sorting costs O(log n) per operation; insertion order costs only memory.
| Kind | Iteration order | Example | add / contains |
|---|---|---|---|
| Unordered | Unspecified | HashSet | O(1) expected |
| Ordered | Order of insertion | LinkedHashSet | O(1) expected |
| Sorted | By comparison | TreeSet | O(log n) |
Same values, different collections
Interactive · edit the inputArrayList
LinkedHashSet
HashSet (capacity 16)
TreeSet
PriorityQueue array
Five collections receive exactly the same values in the same order. Watch how each one stores them.
1List<Integer> list = new ArrayList<>();2Set<Integer> linked = new LinkedHashSet<>();3Set<Integer> hash = new HashSet<>();4Set<Integer> tree = new TreeSet<>();5PriorityQueue<Integer> heap = new PriorityQueue<>();6for (int x : arr) {7list.add(x); // keeps insertion order, keeps duplicates8linked.add(x); // keeps insertion order, drops duplicates9hash.add(x); // order decided by hash buckets10tree.add(x); // always sorted11heap.add(x); // heap array: only the smallest is guaranteed first12}13System.out.println(list + " " + linked + " " + hash + " " + tree + " " + heap);
Live state
values
8
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | values=8 | Five collections receive exactly the same values in the same order. Watch how each one stores them. |
| 2 | L7 | x=42 adds so far=1 distinct=1 HashSet capacity=16 | 42 goes to bucket 10 of 16 in the HashSet (index = (h ^ (h >>> 16)) & 15). |
| 3 | L7 | x=7 adds so far=2 distinct=2 HashSet capacity=16 | 7 goes to bucket 7 of 16 in the HashSet (index = (h ^ (h >>> 16)) & 15). |
| 4 | L7 | x=19 adds so far=3 distinct=3 HashSet capacity=16 | 19 goes to bucket 3 of 16 in the HashSet (index = (h ^ (h >>> 16)) & 15). |
| 5 | L7 | x=7 adds so far=4 distinct=3 HashSet capacity=16 | 7 is already present: the ArrayList and the PriorityQueue keep a second copy, the three sets ignore it. |
| 6 | L7 | x=3 adds so far=5 distinct=4 HashSet capacity=16 | 3 goes to bucket 3 of 16 in the HashSet (index = (h ^ (h >>> 16)) & 15). |
| 7 | L7 | x=25 adds so far=6 distinct=5 HashSet capacity=16 | 25 goes to bucket 9 of 16 in the HashSet (index = (h ^ (h >>> 16)) & 15). |
| 8 | L7 | x=16 adds so far=7 distinct=6 HashSet capacity=16 | 16 goes to bucket 0 of 16 in the HashSet (index = (h ^ (h >>> 16)) & 15). |
| 9 | L7 | x=33 adds so far=8 distinct=7 HashSet capacity=16 | 33 goes to bucket 1 of 16 in the HashSet (index = (h ^ (h >>> 16)) & 15). |
| 10 | L13 | distinct=7 capacity=16 | Same values, five different orders. Only the TreeSet is sorted; the HashSet order is an implementation detail you must never depend on, and the PriorityQueue promises only that index 0 is the minimum. |
Practice ladder
Now try it yourself
Easy
- Contains Duplicate
- Intersection of Two Arrays
Medium
- Longest Consecutive Sequence
- Happy Number
Advanced
- Contains Duplicate III (use TreeSet.ceiling)
- Insert Delete GetRandom O(1)
Key takeaways
- HashSet for membership, LinkedHashSet for insertion order, TreeSet for sorted order and floor/ceiling.
- HashSet is a HashMap whose values are all the same dummy object.
- Unordered, ordered and sorted are different promises; choose the weakest one that works.