TreeMap and TreeSet in Java
Explore sorted key-value and unique collections: Red-Black tree internals, O(log n) operations, and when to choose tree-based maps.
TreeMap and TreeSet in Java
TreeMap and TreeSet provide sorted, ordered collections backed by a Red-Black tree data structure. Unlike HashMap and HashSet which offer O(1) average operations but no ordering guarantees, TreeMap and TreeSet maintain elements in sorted order and provide O(log n) worst-case performance for all operations.
Introduction
TreeMap and TreeSet are the sorted counterparts to HashMap and HashSet in Java’s collection framework. Where HashMap provides O(1) average-case operations with no ordering guarantees, TreeMap maintains keys in sorted order and guarantees O(log n) worst-case performance for every operation. This guarantee matters when you need deterministic performance, range queries, or ordered iteration — scenarios where HashMap’s O(n) worst-case is unacceptable.
The underlying data structure is a Red-Black tree — a self-balancing binary search tree that maintains O(log n) height through color flips and rotations during insertions and deletions. This means no pathological degradation even with adversarial insertion sequences, unlike a naive BST that could become a linked list with sorted input. TreeSet is implemented identically to TreeMap, using a dummy value internally, so everything here applies equally to both.
This post covers when to choose TreeMap over HashMap (and TreeSet over HashSet), how the Red-Black tree maintains balance, the O(log n) complexity guarantees for all operations, the null key restriction and why it exists, range operations like subMap, headMap, and tailMap, and the nearest-key methods (lowerKey, floorKey, ceilingKey, higherKey) that make TreeMap powerful for navigation problems.
When to Use TreeMap
Use TreeMap when:
- You need key-value pairs sorted by key
- You need range operations (subMap, headMap, tailMap)
- You need O(log n) guaranteed performance
- You need to find the closest key below or above a given key
Do not use TreeMap when:
- O(1) performance is critical and ordering is not needed
- You have extremely high write throughput (TreeMap is slower than HashMap)
- You need to store
nullkeys (TreeMap does not allownullkeys)
TreeSet Use Cases
Use TreeSet when:
- You need sorted unique elements
- You need to find the smallest or largest element
- You need range queries on a sorted set
Do not use TreeSet when:
- You need O(1) containment checks and do not need ordering
- You need insertion-order preservation
Red-Black Tree Fundamentals
TreeMap uses a Red-Black tree — a self-balancing binary search tree that guarantees O(log n) height by recoloring and rotating nodes during insertions and deletions. The tree maintains these invariants:
- Every node is either red or black
- The root is black
- All leaves (NIL nodes) are black
- Red nodes cannot have red children
- Every path from a node to its leaves has the same number of black nodes
Red-Black Tree Structure
graph TD
A["8 (Black)"] --> B["3 (Red)"]
A --> H["10 (Black)"]
B --> C["1 (Black)"]
B --> D["6 (Red)"]
C --> E["NIL"]
C --> F["NIL"]
D --> G["NIL"]
D --> I["NIL"]
H --> J["NIL"]
H --> K["NIL"]
TreeMap vs HashMap Comparison
graph LR
A["TreeMap<K,V><br/>Sorted<br/>O(log n) operations<br/>Red-Black tree"] --> B["Use when:<br/>- Ordering required<br/>- Range queries<br/>- Guaranteed performance"]
C["HashMap<K,V><br/>Unsorted<br/>O(1) avg operations<br/>Hash table"] --> D["Use when:<br/>- Fast lookups<br/>- No ordering needed<br/>- High throughput"]
Failure Scenarios
| Scenario | Cause | Result |
|---|---|---|
NullPointerException | Inserting null key (TreeMap requires natural ordering or comparator) | Runtime crash |
ClassCastException | Keys that cannot be compared | Runtime crash |
NoSuchElementException | Calling firstKey() / lastKey() on empty map | Runtime crash |
IllegalArgumentException | Inconsistent ordering between modifications | Runtime crash |
Trade-Off Table
| Aspect | TreeMap | HashMap |
|---|---|---|
| Ordering | Sorted by key | None |
| Get/Put complexity | O(log n) guaranteed | O(1) average, O(n) worst |
| Range operations | Yes (subMap, headMap) | No |
null key | Not allowed | One allowed |
| Memory overhead | Lower (no pointer per entry) | Higher (bucket array) |
| Iteration order | Sorted | Undefined |
Code Snippets
Basic TreeMap Operations
Map<String, Integer> scores = new TreeMap<>();
scores.put("Alice", 95);
scores.put("Bob", 82);
scores.put("Charlie", 91);
System.out.println(scores.firstKey()); // "Alice"
System.out.println(scores.lastKey()); // "Charlie"
System.out.println(scores.lowerEntry("Bob")); // Alice's entry
System.out.println(scores.subMap("Alice", "Charlie")); // Alice + Bob
TreeSet Operations
TreeSet<Integer> nums = new TreeSet<>();
nums.addAll(List.of(5, 2, 8, 1, 9));
System.out.println(nums.first()); // 1
System.out.println(nums.last()); // 9
System.out.println(nums.lower(5)); // 2
System.out.println(nums.higher(5)); // 8
System.out.println(nums.subSet(2, 8)); // [2, 5] — inclusive lower, exclusive upper
Custom Comparable Key
class Person implements Comparable<Person> {
private final String name;
private final int age;
@Override
public int compareTo(Person o) {
int cmp = this.name.compareTo(o.name);
return cmp != 0 ? cmp : Integer.compare(this.age, o.age);
}
}
Observability Checklist
- Monitor tree depth — depth should remain O(log n) despite many insertions
- Track
ClassCastExceptionin production to detect type mismatches in TreeMap keys - Profile compare operations in hot paths — compare is O(log n) per operation
- Log
subMap()calls to detect range query patterns that may indicate full-scan alternatives - Monitor
NullPointerExceptionfromnullkey attempts
Security Notes
TreeMapandTreeSetare not thread-safe- Sorted order means that iterating over the collection reveals the sorted arrangement of data — consider this when the sorted order itself is sensitive
- The comparator or
Comparable.compareTo()should not leak sensitive state Collections.unmodifiableMap()andSet.of()create immutable views for security-sensitive use cases
Common Pitfalls / Anti-Patterns
nullkey:TreeMapdoes not allownullkeys because it usescompareTo()which would throwNullPointerExceptionif the key is null. UseHashMapif you neednullkey support.- Inconsistent comparators: If you use a
TreeMap(Comparator)and then modify the comparator’s behavior, the map becomes corrupted - Confusing
subMap()bounds:subMap(from, to)is half-open — inclusive offrom, exclusive ofto TreeSetvsTreeMap:TreeSetis toTreeMapasHashSetis toHashMap—TreeSetinternally uses aTreeMapwith dummy values, just likeHashSetusesHashMap- Comparator vs Comparable: If elements implement
Comparable, you can use the no-arg constructor; otherwise, pass aComparatorto the constructor
Quick Recap
TreeMapis a sorted key-value map backed by a Red-Black tree;TreeSetwraps aTreeMapinternally- All operations are O(log n) guaranteed — no worst-case degradation
- Elements must be mutually comparable — either implement
Comparableor provide aComparator nullkeys are not allowed inTreeMap- Range operations (
subMap,headMap,tailMap) are available and O(log n)
Interview Questions
Model Answer: "A Red-Black tree is a self-balancing binary search tree where no path is more than twice as long as any other. TreeMap uses it to guarantee O(log n) worst-case time complexity for put(), get(), remove(), and contains(). Without balancing, a sorted insertion sequence would produce a degenerate O(n) linked-list tree. Red-Black trees maintain balance via color flips and rotations with O(log n) cost per operation."
Model Answer: "All single-element operations (get, put, remove, containsKey) are O(log n) guaranteed. Range operations (subMap, headMap, tailMap) are also O(log n) to find the starting point plus O(k + log n) to retrieve k elements. This is in contrast to HashMap's O(1) average but O(n) worst-case."
Model Answer: "TreeMap locates entries by comparing keys using compareTo() (for Comparable keys) or compare() (for Comparator). Calling these methods on a null key throws NullPointerException. HashMap can handle null keys because it uses hashCode() and equals() which handle null explicitly. TreeMap requires a total ordering, which null violates."
Model Answer: "TreeSet is implemented identically to HashSet — as a wrapper around a map. For TreeSet, it wraps a TreeMap
Model Answer: "These are all O(log n) operations returning the nearest key relative to a given key: lowerKey(k) returns greatest key strictly less than k; floorKey(k) returns greatest key less than or equal to k; ceilingKey(k) returns smallest key greater than or equal to k; higherKey(k) returns smallest key strictly greater than k. Each returns null if no such key exists. floor and ceiling include the key itself; lower and higher do not."
Model Answer: "TreeMap uses Red-Black tree invariants. On insertion, the new node is colored red and inserted as a leaf. If a red-red violation occurs (parent is also red), the tree performs rotations and recoloring to restore balance. This is done bottom-up with parent pointer traversal, guaranteeing O(log n) rebalancing per insertion."
Model Answer: "subMap(from, to) is O(log n) to locate the starting point and returns a view backed by the original map (not a copy). Operations on the submap (add, remove) are reflected in the original. The submap is half-open: [from, to) — inclusive of from, exclusive of to."
Model Answer: "TreeSet stores elements in a sorted order using compareTo() (or compare()). Calling compareTo() on null throws NullPointerException. HashSet uses hashCode() and equals() which explicitly handle null. TreeSet requires a total ordering, which null violates by definition."
Model Answer: "TreeMap uses compare() (not equals()) to determine key equivalence: two keys k1 and k2 are considered equal if comparator.compare(k1, k2) == 0. This means if two keys are equals() but compare() returns non-zero, they are treated as distinct keys in the map — a subtle distinction when using custom comparators."
Model Answer: "NavigableMap extends SortedMap with navigation methods: lowerEntry(), floorEntry(), ceilingEntry(), higherEntry() (returning entries not just keys), subMap() with boolean flags for inclusivity, and descendingMap() returning a reversed view. TreeMap implements NavigableMap."
Model Answer: "Yes, but HashMap is typically better for frequency counting due to O(1) average operations. TreeMap's advantage would be if you need sorted keys (e.g., displaying frequencies in alphabetical order) or range queries (e.g., sum of frequencies between keys). For most frequency map use cases, HashMap is the standard choice."
Model Answer: "All return views backed by the original TreeMap. subMap(from, to) returns entries with keys in [from, to) (inclusive lower, exclusive upper). headMap(to) returns entries with keys less than to. tailMap(from) returns entries with keys greater than or equal to from. All are O(log n) to create and support operations reflected in the original map."
Model Answer: "TreeSet uses the comparator or compareTo() to determine equality: if compare(e1, e2) == 0, the elements are considered equal and the second insert is ignored (add returns false). This differs from HashSet which uses equals() for the same purpose. Two different objects that are equals() but not comparable can coexist in TreeSet."
Model Answer: "On deletion, if the node has two children, it is replaced by its successor (or predecessor), then deleted. If the deletion breaks Red-Black invariants, the tree performs rotations and recoloring (similar to insertion) to restore balance. This guarantees O(log n) deletion time."
Model Answer: "pollFirstEntry() removes and returns the first entry (or null if empty) — an atomic operation. remove() on firstKey() requires two calls (first to get key, then to remove) and has a race condition window. Use pollFirstEntry() when you need atomic remove-and-fetch for the smallest key."
Model Answer: "merge(key, value, remappingFunction) adds the key-value pair if absent, or updates existing value using the remapping function. If the remapping function returns null, the key is removed. If the key is absent and value is non-null, the pair is added directly. This is useful for implementing word frequency counters and similar patterns."
Model Answer: "NavigableSet extends SortedSet with navigation methods: lower(), floor(), ceiling(), higher() (returning elements), subSet() with inclusivity flags, and descendingSet() for reversed iteration. TreeSet implements NavigableSet. All these methods work on element ordering, not index."
Model Answer: "No. TreeSet requires a total ordering on elements — either elements must implement Comparable with a consistent compareTo(), or a Comparator must be provided at construction. Attempting to insert incomparable elements throws ClassCastException during the comparison operation."
Model Answer: "first() returns the smallest element (or null if empty) without removing it. last() returns the largest element (or null if empty) without removing it. pollFirst() removes and returns the smallest element (or null if empty) — an atomic remove-and-fetch. Use first()/last() when you need to inspect without consuming; use pollFirst()/pollLast() when you need to atomically remove and process the boundary element."
Model Answer: "descendingSet() returns a view of the TreeSet in reverse order — it is an O(1) operation that returns a NavigableSet backed by the original TreeMap. Operations on the descending set (iteration, add, remove) are reflected in the original. The descending set is backed by the same underlying Red-Black tree, so all operations remain O(log n). This is the standard way to iterate TreeSet in descending order without creating a copy."
Further Reading
- Oracle TreeMap Documentation — Official API specification
- Baeldung: TreeMap Internals — Red-Black tree mechanics and sorted map operations
- Red-Black Tree Visualization — Interactive visualization of Red-Black tree insertion and deletion operations
- TreeSet vs HashSet vs LinkedHashSet — Comprehensive comparison of Set implementations
Conclusion
TreeMap and TreeSet are the ordered counterparts to HashMap and HashSet. Where HashMap prioritizes speed with O(1) average operations, TreeMap guarantees O(log n) worst-case performance and maintains keys in sorted order. That tradeoff matters when you need range queries, sorted iteration, or guaranteed performance without hash collision risk.
The null key restriction is the most common stumbling block — TreeMap requires a total ordering, which null cannot satisfy. For nearest-key operations like lowerKey() and ceilingKey(), TreeMap excels at O(log n) performance. TreeSet is simply TreeMap with dummy values, so everything here applies equally.
TreeMap and TreeSet are the natural next step after HashMap and HashSet when ordering becomes a requirement. If you also need queue or deque behavior, Queue and Deque covers heap-based priority ordering.
Category
Related Posts
Abstract Classes in Java
Learn about partially implemented classes that define contracts for subclasses using abstract methods and concrete implementations.
Arithmetic Operators in Java
Master Java arithmetic operators: addition, subtraction, multiplication, division, and modulo with integer division gotchas and operator precedence explained.
Array Basics in Java
Learn Java array fundamentals: declaration, initialization, element access, and the length property explained simply.