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.

published: reading time: 14 min read author: GeekWorkBench

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 null keys (TreeMap does not allow null keys)

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:

  1. Every node is either red or black
  2. The root is black
  3. All leaves (NIL nodes) are black
  4. Red nodes cannot have red children
  5. 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

ScenarioCauseResult
NullPointerExceptionInserting null key (TreeMap requires natural ordering or comparator)Runtime crash
ClassCastExceptionKeys that cannot be comparedRuntime crash
NoSuchElementExceptionCalling firstKey() / lastKey() on empty mapRuntime crash
IllegalArgumentExceptionInconsistent ordering between modificationsRuntime crash

Trade-Off Table

AspectTreeMapHashMap
OrderingSorted by keyNone
Get/Put complexityO(log n) guaranteedO(1) average, O(n) worst
Range operationsYes (subMap, headMap)No
null keyNot allowedOne allowed
Memory overheadLower (no pointer per entry)Higher (bucket array)
Iteration orderSortedUndefined

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 ClassCastException in 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 NullPointerException from null key attempts

Security Notes

  • TreeMap and TreeSet are 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() and Set.of() create immutable views for security-sensitive use cases

Common Pitfalls / Anti-Patterns

  1. null key: TreeMap does not allow null keys because it uses compareTo() which would throw NullPointerException if the key is null. Use HashMap if you need null key support.
  2. Inconsistent comparators: If you use a TreeMap(Comparator) and then modify the comparator’s behavior, the map becomes corrupted
  3. Confusing subMap() bounds: subMap(from, to) is half-open — inclusive of from, exclusive of to
  4. TreeSet vs TreeMap: TreeSet is to TreeMap as HashSet is to HashMapTreeSet internally uses a TreeMap with dummy values, just like HashSet uses HashMap
  5. Comparator vs Comparable: If elements implement Comparable, you can use the no-arg constructor; otherwise, pass a Comparator to the constructor

Quick Recap

  • TreeMap is a sorted key-value map backed by a Red-Black tree; TreeSet wraps a TreeMap internally
  • All operations are O(log n) guaranteed — no worst-case degradation
  • Elements must be mutually comparable — either implement Comparable or provide a Comparator
  • null keys are not allowed in TreeMap
  • Range operations (subMap, headMap, tailMap) are available and O(log n)

Interview Questions

1. What is a Red-Black tree and why does TreeMap use it?

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."

2. What is the time complexity of TreeMap operations?

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."

3. Why doesn't TreeMap allow null keys?

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."

4. How does TreeSet internally work?

Model Answer: "TreeSet is implemented identically to HashSet — as a wrapper around a map. For TreeSet, it wraps a TreeMap (with the shared PRESENT dummy value), just as HashSet wraps a HashMap. This means TreeSet inherits all the ordering and complexity guarantees of TreeMap, but requires that elements be comparable."

5. What is the difference between lowerKey(), floorKey(), ceilingKey(), and higherKey()?

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."

6. How does TreeMap maintain balance during insertions?

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."

7. What is the time complexity of TreeMap's subMap() operation?

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."

8. Why does TreeSet not allow null elements but HashSet does?

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."

9. How does TreeMap's comparator affect equality?

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."

10. What is a NavigableMap and how does it extend SortedMap?

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."

11. Can TreeMap be used as a frequency map or histogram?

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."

12. What is the difference between subMap(), headMap(), and tailMap()?

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."

13. How does TreeSet handle duplicate element insertion?

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."

14. How does the Red-Black tree maintain balance during deletions?

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."

15. What is the difference between pollFirstEntry() and remove() for the first entry?

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."

16. How does TreeMap's merge() method work?

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."

17. What is the relationship between TreeSet and NavigableSet?

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."

18. Can TreeSet store elements that are not mutually comparable?

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."

19. What is the difference between TreeSet's first(), last(), and pollFirst() methods?

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."

20. How does TreeSet's descendingSet() method work and what is its time complexity?

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

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.

#java-abstract-classes #java #java-fundamentals

Arithmetic Operators in Java

Master Java arithmetic operators: addition, subtraction, multiplication, division, and modulo with integer division gotchas and operator precedence explained.

#java-arithmetic-operators #java #java-fundamentals

Array Basics in Java

Learn Java array fundamentals: declaration, initialization, element access, and the length property explained simply.

#java-array-basics #java #java-fundamentals