TreeMap/TreeSet maintain automatic sorted order via Red-Black Tree
Keys/elements must implement Comparable or receive a Comparator
Range queries (subMap, headSet) are O(log n) - the killer feature
Two elements are equal if compare() returns 0, not if equals() says so
Null keys throw NullPointerException; null values are allowed
Mutation of key fields after insertion silently corrupts the tree
✦ Definition~90s read
What is TreeMap and TreeSet in Java?
TreeSet and TreeMap are Java's sorted collection implementations, backed by a Red-Black tree. Unlike HashSet or HashMap, they don't rely on hashCode() and equals() for uniqueness — they use compareTo() (or a provided Comparator) for both ordering AND deduplication.
★
Imagine a library where books are always kept in alphabetical order on the shelves — no matter when a new book arrives, the librarian slots it into exactly the right spot.
This is the critical gotcha: if your comparator returns 0 for two objects that aren't logically equal, one silently disappears. No exception, no warning — just a dropped element. This behavior makes them ideal when you need a sorted, unique set or a map with ordered keys, but dangerous when your comparison logic doesn't align with object identity.
In practice, TreeSet and TreeMap shine when you need range queries, nearest-neighbor lookups, or sorted iteration — features absent from their hash-based counterparts. They implement NavigableSet and NavigableMap, giving you methods like ceiling(), floor(), subSet(), and headMap().
For example, a TreeMap of timestamps lets you find "all events between 2 PM and 3 PM" in O(log n) time. But if you don't need ordering, HashMap and HashSet are faster (O(1) vs O(log n)) and don't have the silent-dedup trap.
When to avoid them: if your objects' natural ordering changes (e.g., mutable fields used in compareTo), you'll corrupt the tree. If you need insertion-order preservation, use LinkedHashSet/LinkedHashMap. And if you're working with large datasets where O(log n) matters, consider concurrent alternatives like ConcurrentSkipListMap.
The real power of TreeSet/TreeMap isn't sorting — it's the navigable API that makes them the right tool for sorted range operations, not general-purpose collection needs.
Plain-English First
Imagine a library where books are always kept in alphabetical order on the shelves — no matter when a new book arrives, the librarian slots it into exactly the right spot. That's a TreeSet or TreeMap. Unlike a pile of books on the floor (a HashMap), every insertion maintains perfect order automatically. You pay a small price in speed, but you gain the superpower of always knowing what's first, last, or nearest to any value.
Most developers think a HashMap is the answer to everything—until they need to iterate in sorted order or find the nearest key. That’s where TreeMap and TreeSet come in: they keep your keys sorted at all times, enforce uniqueness as a side effect, and let you navigate with methods like lowerKey and ceilingEntry. Skip them, and you’ll end up manually sorting collections or writing brittle rank-loops that break the moment your data grows.
How TreeMap and TreeSet Use Comparison to Both Sort and Deduplicate
TreeMap and TreeSet are Red-Black tree implementations in Java that maintain elements in sorted order. The critical mechanic: they use a Comparator (or natural ordering) for both sorting and equality checks. If the Comparator returns 0 for two elements, the map or set treats them as duplicates — the second insertion silently replaces the first. This differs from HashSet/HashMap, which use equals() and hashCode().
When you insert into a TreeSet, the tree walks from the root comparing keys. If the Comparator returns 0 at any node, the existing entry is overwritten; no new node is created. This means two objects that are not equal() but compare as 0 will be treated as identical. The same applies to TreeMap keys. The tree guarantees O(log n) for contains, add, and remove operations.
Use TreeSet/TreeMap when you need a sorted collection and can accept that comparison logic defines identity. Common use cases: maintaining a sorted set of timestamps, implementing range queries (subSet, headSet, tailSet), or building an ordered cache. Never use them when objects have distinct identities but compare equal — you will silently lose data.
⚠ Comparator equals() mismatch
If Comparator returns 0 but equals() returns false, TreeSet will drop objects without error — a common source of hard-to-find bugs.
📊 Production Insight
A financial trading system used TreeSet for order book levels where two price levels differed by less than a tick but the Comparator rounded to the same bucket — orders were silently dropped.
Symptom: orders disappeared from the book with no exception, causing incorrect fills and reconciliation failures.
Rule: always ensure Comparator consistency with equals() when using TreeSet/TreeMap for identity-sensitive data.
🎯 Key Takeaway
TreeSet/TreeMap use Comparator for both ordering and identity — not equals().
A Comparator returning 0 causes silent overwrite, not a collision.
Use TreeSet only when comparison-based identity matches object identity.
thecodeforge.io
Treemap Treeset Java
TreeSet — When Order Is Non-Negotiable
A TreeSet is a Set — so it never stores duplicates — but unlike HashSet it keeps every element in its natural sorted order at all times. 'Natural order' means whatever Comparable defines for that type: alphabetical for Strings, ascending for numbers, chronological for dates.
The real payoff isn't just iteration order. It's the NavigableSet API: methods like first(), last(), floor(), ceiling(), headSet(), and tailSet() let you slice and query your sorted data without loops. Want every username that comes after 'mike' alphabetically? One line. Want the five closest scores below a threshold? One line.
Internally, TreeSet wraps a TreeMap and stores your elements as keys. Every add(), remove(), and contains() runs in O(log n) — slower than HashSet's O(1) average, but a fair trade when you genuinely need sorted access. Don't use TreeSet by default; use it when the ordering itself is part of the feature you're building.
All scores (always sorted): [1500, 3300, 4200, 8750, 9100]
Lowest score : 1500
Highest score: 9100
Highest score at or below 4000: 3300
Main bracket [3000, 9000): [3300, 4200, 8750]
💡Pro Tip: Views Are Live
headSet(), tailSet(), and subSet() don't copy data — they return a live view backed by the original TreeSet. If you add a score that falls within the range after creating the view, it appears in the view automatically. This is great for efficiency but can bite you if you mutate the set while iterating the view.
📊 Production Insight
If you need range queries, TreeSet is the only Set implementation that provides them in O(log n).
But if you only iterate the set once, consider sorting a List instead — it avoids the overhead of preserving order on every insert.
Rule: use TreeSet when you query ranges more than you insert.
🎯 Key Takeaway
TreeSet wraps a TreeMap and uses NavigableSet for range slicing.
Live views (headSet, subSet) reflect changes to the backing set.
Don't use TreeSet unless you actually call first(), last(), or range methods.
TreeMap — Sorted Keys With All the Power of a Map
TreeMap is to HashMap what TreeSet is to HashSet: it keeps all its keys in sorted order at all times, and it exposes a rich NavigableMap API for range queries, boundary lookups, and sliced views.
The classic use case is any kind of timeline, schedule, or rank-ordered dataset. Imagine you're building a task scheduler. Each task has a deadline (the key) and a description (the value). A TreeMap lets you instantly find the next task due, all tasks due before a certain date, or every task in a given window — without scanning the entire map.
TreeMap also implements SortedMap and NavigableMap, which means you get firstKey(), lastKey(), floorKey(), ceilingKey(), lowerKey(), higherKey(), headMap(), tailMap(), and subMap() — all backed by the same O(log n) Red-Black Tree. You can even get a descending view of the entire map with descendingMap(). These aren't convenience extras; they're the whole reason to choose TreeMap over HashMap.
TreeMap uses a Red-Black Tree specifically because it guarantees O(log n) worst-case for insert, delete, and lookup. A plain binary search tree can degrade to O(n) if elements are inserted in sorted order. Red-Black Trees self-balance on every mutation, so worst-case is always logarithmic — that's why Java's designers chose it.
📊 Production Insight
TreeMap's sorted order adds ~40% overhead vs HashMap for single-key operations.
But for range queries, it's orders of magnitude faster than scanning a HashMap.
Rule: if you never use floorKey(), headMap(), or subMap(), you're paying for sorting you don't need.
🎯 Key Takeaway
TreeMap provides O(log n) range queries via NavigableMap.
descendingMap() gives reverse order without extra cost.
Use TreeMap when your primary access pattern is sorted iteration or range lookups.
thecodeforge.io
Treemap Treeset Java
NavigableMap and NavigableSet — The Real Reason to Choose Tree
The true power of TreeMap and TreeSet lies in their Navigable interfaces — not just sorted iteration, but the ability to slice, dice, and query your data in logarithmic time. These methods don't copy data; they return live views that reflect ongoing mutations.
floorKey(k) / ceilingKey(k): Find the nearest key less than/equal or greater than/equal to a target.
lowerKey(k) / higherKey(k): Strict versions of above (exclusive).
headMap(k, inclusive): All keys less than (or ≤) k.
tailMap(k, inclusive): All keys greater than (or ≥) k.
subMap(from, fromInclusive, to, toInclusive): A contiguous slice.
descendingMap() / descendingSet(): Reverse-order views without sorting copy.
pollFirstEntry() / pollLastEntry(): Retrieve and remove boundary entries.
Use these when you need to answer 'what's the next available slot?', 'find all orders within a date range', or 'get the top 5 scores' without ever calling sort().
Think of TreeMap as a dynamic sorted index—like the index at the back of a book, but always up to date.
floor/lower find the page before or at the topic
headMap/tailMap give you the entire chapter before or after
subMap is the exact page range you're looking for
descendingMap flips the book upside down without reprinting it
📊 Production Insight
Live views are efficient but dangerous: modifying the parent collection while iterating a view throws ConcurrentModificationException.
If you need a snapshot, copy the view into a new ArrayList or HashSet.
Rule: use views for read-heavy patterns; copy when you need to mutate independently.
🎯 Key Takeaway
NavigableMap's range methods are O(log n) and return live views.
Ceiling/floor are for 'nearest neighbor' queries.
Descending views cost no extra memory or time.
Custom Ordering With Comparator — When Natural Order Isn't Enough
Natural order works great for Strings and numbers, but what about your own objects? And what if you want reverse order, or a case-insensitive sort? That's where a Comparator comes in.
Both TreeMap and TreeSet accept a Comparator in their constructor. Once you supply one, it completely overrides natural order — even if your objects implement Comparable. This is powerful, but there's a critical rule: the Comparator you use for ordering becomes the equality definition for the collection. Two elements are considered duplicates if comparator.compare(a, b) == 0, not if a.equals(b). This surprises many developers.
A practical pattern: pass Comparator.reverseOrder() to get a descending TreeMap, which is perfect for leaderboards where you want the highest value first. Or pass String.CASE_INSENSITIVE_ORDER to a TreeSet of strings so 'Apple' and 'apple' are treated as the same entry. The Comparator is your formatting rule for the entire collection.
If your Comparator returns 0 for two objects that equals() considers different, the TreeSet or TreeMap will treat them as the same element and silently discard the second one. Always make your Comparator consistent with equals() — or at least be deliberate when it isn't (like the case-insensitive example above, where the behaviour is intentional).
📊 Production Insight
A Comparator that is inconsistent with equals() is a common source of data loss.
Test with duplicate sort keys: write a unit test that inserts two objects with the same sort value and verify both are present if they have different identities.
Rule: always add a tiebreaker (thenComparing) that uses a unique identifier.
🎯 Key Takeaway
Comparator overrides natural order and defines equality.
Inconsistent comparator vs equals() causes silent data loss.
Always use .thenComparing() with a unique field to distinguish objects.
TreeMap vs HashMap vs TreeSet vs HashSet — Choosing the Right Tool
The most common mistake is defaulting to HashMap or HashSet out of habit and then discovering you need sorted access later — at which point you're either converting structures or adding sort calls everywhere. Choosing right from the start costs nothing.
The decision is straightforward: if you need to ask range questions ('everything between X and Y'), boundary questions ('what's the closest element to Z?'), or if iteration order must be predictable and sorted, use the Tree variant. If you only ever ask point questions ('does this key exist?', 'give me this value') and don't care about order, stick with the Hash variant for its faster O(1) average operations.
LinkedHashMap and LinkedHashSet are the middle ground — they maintain insertion order but don't sort. Use them when you want predictable iteration without the overhead of sorting. The table below captures the key trade-offs so you can settle this question in seconds during a code review.
new TreeMap<>(existingHashMap) creates a sorted copy in O(n log n). This is a common pattern: build fast with HashMap during heavy write phases, then wrap in a TreeMap for a read-heavy, sorted-output phase. Just remember it's a copy — mutations to one don't affect the other.
📊 Production Insight
Using TreeMap when HashMap suffices wastes memory (tree node pointers) and time (log vs 1).
But retrofitting sorting onto a HashMap later is expensive: you'll either sort on every read or maintain a separate index.
Rule: choose sorted collection at design time if you need range queries, never as a default.
🎯 Key Takeaway
TreeMap/TreeSet for sorted iteration and range queries.
HashMap/HashSet for maximum speed on point queries.
LinkedHashMap/LinkedHashSet for insertion-order without sorting overhead.
TreeMap vs HashMap — Detailed Comparison
While both implement Map, TreeMap and HashMap serve fundamentally different purposes. The table below highlights the key differences you need to consider when choosing.
This table is a quick reference for code reviews and design decisions. When in doubt, ask: "Do I need to answer 'what's between X and Y'?" If yes, TreeMap is the answer.
📊 Production Insight
Many teams default to HashMap and retrofit sorting later. That's often acceptable if the data volume is small, but for large datasets the performance cost of sorting on every read is substantial. Use TreeMap from the start when range queries are a first-class requirement.
🎯 Key Takeaway
TreeMap offers sorted iteration and range queries at O(log n); HashMap offers O(1) average point operations. Choose based on whether you need sorted access.
TreeSet vs HashSet — Detailed Comparison
The same trade-offs apply to the Set variants. Here's a direct comparison.
Feature
TreeSet
HashSet
Internal structure
Red-Black Tree (backed by TreeMap)
Hash Table
Iteration order
Always sorted
Unpredictable
add / contains / remove
O(log n)
O(1) average, O(n) worst
Range queries
Built-in (subSet, headSet) O(log n)
Not supported
Null elements
Not allowed (NPE)
One null allowed
Thread safety
Not thread-safe
Not thread-safe
Memory overhead
Higher (tree node pointers)
Lower
Best used when
Need sorted elements or range searches
Maximum performance, order irrelevant
Use case example
Unique sorted scores, auto-complete lists
Deduplication, presence checks
Like the Map counterparts, the decision hinges on whether you ever need ordered iteration or range queries. If you only need uniqueness and fast membership tests, HashSet is the better choice.
📊 Production Insight
TreeSet is often mistakenly used for 'unique list that needs to be sorted'. If the collection is only iterated once (e.g., at the end of a batch), consider collecting into a HashSet first and then sorting into a list — that can be faster than maintaining a TreeSet throughout the operation.
🎯 Key Takeaway
TreeSet provides sorted iteration and range queries; HashSet provides faster point operations. Choose TreeSet only when you need sorted access.
Advantages and Disadvantages of TreeMap and TreeSet
Both TreeMap and TreeSet share the same underlying data structure (Red-Black Tree), so their strengths and weaknesses are nearly identical.
Advantages
Advantage
Explanation
Automatic sorting
Elements are always in order without manual sorting.
Rich Navigable API
floor, ceiling, headSet, subSet, descendingMap — all O(log n).
Live views
Range views reflect mutations to the backing collection instantly.
Guaranteed log(n) performance
All operations are O(log n) worst-case, no performance cliffs.
Comparator injection
Custom ordering without modifying the element class.
Natural ordering fallback
Works with any Comparable type out of the box.
Disadvantages
Disadvantage
Explanation
Slower than hash-based counterparts
O(log n) vs O(1) for get/put/add. 40% more overhead for single-key ops.
No null keys/elements
NullPointerException on null keys/elements.
Higher memory consumption
Tree nodes store pointers to left, right, parent, and color (Red/Black).
Comparator inconsistency risk
Silent data loss if compare() returns 0 for non-identical objects.
Not thread-safe
Requires external synchronization or ConcurrentSkipListMap for concurrent access.
Key mutation danger
Mutating key fields after insertion breaks the tree without warning.
These trade-offs are fundamental to the data structure. Use the advantages when you need sorted ordering; avoid the disadvantages by carefully designing keys and comparators.
📊 Production Insight
In high-throughput systems, the 40% overhead of TreeMap vs HashMap can be significant. Profile your application: if range queries are rare, consider using a HashMap and sorting only when required (e.g., at endpoint level). If range queries are frequent, the TreeMap's O(log n) range slicing will vastly outperform sorting a HashMap on each request.
🎯 Key Takeaway
TreeMap/TreeSet offer automatic sorting and powerful range queries at the cost of slower single-key operations, no null keys, and higher memory usage.
Beyond the basic floor/ceiling and sub-range views, NavigableMap offers three powerful methods for boundary manipulation: descendingMap(), pollFirstEntry(), and pollLastEntry(). These are especially useful for leaderboards, job queues, and any scenario where you need to process items in order while mutating the collection.
descendingMap(): Returns a reverse-order view of the map. No new data is created; iteration over the descending map is just as fast and memory-cheap as iterating forward. Combined with subMap(), you can easily get slices in descending order.
pollFirstEntry(): Retrieves and removes the entry with the smallest key. This is a single O(log n) operation that both reads and deletes the first entry. Equivalent to firstEntry() + remove(firstKey()) but atomic and more efficient.
pollLastEntry(): Retrieves and removes the entry with the largest key. Same benefits as pollFirstEntry for the other end.
These methods are commonly used in priority queues where you want to serve items in order while draining the structure. They are also great for implementing round-robin or timeout processing without additional locking or iteration.
After pollFirstEntry: {Alice=2500, Bob=1800, Charlie=3200, Eve=2900}
Poll last (highest score): Eve=2900
After pollLastEntry: {Alice=2500, Bob=1800, Charlie=3200}
💡Polling for Job Queues
pollFirstEntry() is ideal for implementing a priority queue where you want to process items in order and remove them from the queue simultaneously. Use TreeMap with a custom Comparator for priority (e.g., deadline, urgency) and pollFirstEntry() in a loop to drain the queue safely.
📊 Production Insight
descendingMap() is particularly useful for leaderboards that need both ascending and descending views. You can keep one TreeMap and call descendingMap() for the 'top scores' view — no separate reverse-sorted structure needed. This halves memory usage and avoids consistency problems.
pollFirstEntry() and pollLastEntry() are atomic for single-threaded use. In concurrent scenarios, wrap them in synchronized blocks or use ConcurrentSkipListMap.
🎯 Key Takeaway
pollFirstEntry/pollLastEntry combine retrieval and removal in one O(log n) operation — ideal for draining a sorted map.
descendingMap() provides reverse order at zero extra cost.
Practice Problems: Interval Scheduling, Range Queries, Sliding Window
The best way to internalize the power of TreeMap and TreeSet is to solve problems that naturally require sorted order and range queries. Below are five practice problems you can implement to sharpen your skills.
1. Interval Scheduling with TreeSet Given a list of intervals (start, end), find the maximum number of non-overlapping intervals you can schedule. Use a TreeSet to store intervals sorted by end time. Iterate through sorted intervals, and when you find one that doesn't overlap with the last scheduled, add it. Demonstrates floor() or last() for boundary checks.
2. Range Sum Queries on a Dynamic Array Maintain a TreeMap of cumulative sums or directly a TreeMap<Integer, Integer> where keys are indices. Support operations: add value at index, query sum between indices. Use subMap() to get entries in the range and compute the sum. Simulates a Fenwick tree without the complexity.
3. Sliding Window Median Given a stream of integers and a window size k, find the median of each window. Use two TreeSets (or one TreeSet with order statistics) to maintain left and right halves. Use last() and first() to get the max of left and min of right. Demonstrates maintaining balance and using boundary methods.
4. Nearest Neighbor in 1D Given a set of points on a line (integers), support insert(x) and query(x) that returns the closest point to x. Use TreeSet and its floor() and ceiling() methods. Compare distances to find nearest. Classic example of why NavigableSet exists.
5. Task Scheduler with Deadline Constraints Given tasks with deadlines and durations, find if all tasks can be scheduled. Use a TreeMap to store tasks sorted by deadline. Iterate from earliest deadline, subtract duration from current time, and if you ever go negative, impossible. Uses firstKey() and pollFirstEntry() efficiently.
Each problem can be solved in < 50 lines of Java using TreeMap/TreeSet. Implement them to gain confidence with the APIs discussed in this article.
📊 Production Insight
These problems mirror real production scenarios: the interval scheduling problem is essentially the same as booking meeting rooms; range sum queries are used in real-time analytics dashboards; sliding window median appears in stock trading volatility calculations. Master these patterns and you'll immediately see opportunities to apply TreeMap/TreeSet in your day job.
🎯 Key Takeaway
Practice problems solidify your understanding of TreeMap/TreeSet's range query capabilities. Focus on problems that require floor/ceiling or sub-range views.
Red-Black Trees Under the Hood — Why O(log n) Isn't Magic
TreeMap and TreeSet don't use some generic sorted container. They're backed by a Red-Black tree — a self-balancing binary search tree that guarantees O(log n) for insert, delete, and lookup. That's not a marketing claim; it's a mathematical guarantee.
Why does the balancing matter? Because without it, a BST degenerates into a linked list when you insert sorted data. Your O(log n) becomes O(n), and your production latency graph looks like a cliff. Red-Black trees enforce five invariants that keep the tree height bounded by 2*log2(n+1). Every insert or delete triggers a rotation and recoloring cascade that maintains balance.
The practical takeaway: TreeMap and TreeSet are predictable. They don't have bad days. HashMap has O(1) average but O(n) worst-case under collision attacks or poor hash distribution. Trees give you consistent performance. When you're building latency-sensitive systems — trading systems, real-time dashboards, rate limiters — that predictability beats raw speed every time.
TreeRotationSimulation.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// io.thecodeforge — java tutorial// Simulate tree depth after inserting sorted dataimport java.util.*;
publicclassTreeRotationSimulation {
publicstaticvoidmain(String[] args) {
TreeSet<Integer> treeSet = newTreeSet<>();
HashSet<Integer> hashSet = newHashSet<>();
List<Integer> sortedData = newArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
sortedData.add(i);
}
// TreeSet: insert sorted datalong start = System.nanoTime();
treeSet.addAll(sortedData);
long treeTime = System.nanoTime() - start;
// HashSet: insert same sorted data
start = System.nanoTime();
hashSet.addAll(sortedData);
long hashTime = System.nanoTime() - start;
System.out.println("TreeSet insert time (millions of nanoseconds): " + treeTime / 1_000_000);
System.out.println("HashSet insert time (millions of nanoseconds): " + hashTime / 1_000_000);
System.out.println("TreeSet size: " + treeSet.size());
System.out.println("HashSet size: " + hashSet.size());
}
}
Output
TreeSet insert time (millions of nanoseconds): 412
HashSet insert time (millions of nanoseconds): 287
TreeSet size: 1000000
HashSet size: 1000000
🔥Performance Reality Check:
TreeSet is ~30% slower than HashSet for bulk inserts of sorted data. But it maintains sorted order without a separate sort pass. The tradeoff is logarithmic vs. constant time — and trees win on consistency.
🎯 Key Takeaway
Red-Black trees guarantee O(log n) always. HashMap doesn't. Choose TreeMap or TreeSet when performance variance matters more than peak throughput.
The NullPointerException Trap — TreeMap Doesn't Tolerate Null Keys
You can stuff null values into a HashMap all day. TreeMap? It throws a NullPointerException in your face the moment you try a null key. This isn't a bug — it's a design decision rooted in comparison logic.
TreeMap sorts keys using either their natural ordering (Comparable) or a provided Comparator. But null isn't an object. You can't call compareTo() on null. So the map punts and fires an exception. However, TreeMap handles null values without complaint — the value side has no ordering constraint.
TreeSet has the same constraint. Since it's backed by a TreeMap, null elements are also rejected. Contrast that with HashSet, which happily accepts null (it goes into bucket 0). This catches devs off guard during refactors: swapping a HashMap for a TreeMap to get sorted iteration suddenly breaks every null-key lookup.
The fix? Either filter nulls before insertion, or write a custom Comparator that explicitly handles nulls by defining an ordering (e.g., nulls first or nulls last). But think hard before doing that — it usually masks a design smell where you shouldn't have null keys at all.
NullKeyTrap.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// io.thecodeforge — java tutorialimport java.util.*;
publicclassNullKeyTrap {
publicstaticvoidmain(String[] args) {
TreeMap<String, Integer> scores = newTreeMap<>();
// This works — null value is fine
scores.put("Alice", null);
try {
// This throws — null key is not
scores.put(null, 100);
} catch (NullPointerException e) {
System.out.println("Caught NPE on null key: " + e.getMessage());
}
// TreeSet analogTreeSet<String> names = newTreeSet<>();
names.add("Bob");
try {
names.add(null);
} catch (NullPointerException e) {
System.out.println("TreeSet also rejects null elements");
}
System.out.println("TreeMap contents: " + scores);
System.out.println("TreeSet contents: " + names);
}
}
Output
Caught NPE on null key: Cannot invoke \"java.lang.Comparable.compareTo()\" because the key is null
TreeSet also rejects null elements
TreeMap contents: {Alice=null}
TreeSet contents: [Bob]
⚠ Production Trap:
Refactoring from HashMap to TreeMap will break any code path that stores null keys. Always audit your keys before switching. Better yet, eliminate null keys from your domain model — they're a code smell.
🎯 Key Takeaway
TreeMap accepts null values but never null keys. TreeSet rejects null entirely. Plan your null strategy before migrating from hash-based collections.
● Production incidentPOST-MORTEMseverity: high
The Case of the Missing Leaderboard Entries
Symptom
Player scores were being lost. The leaderboard had ~1000 expected entries but only showed 50 unique score values.
Assumption
The TreeSet would handle duplicate scores because each player has a different name. The team assumed the Comparator only looked at scores, not player ID.
Root cause
The Comparator for the TreeSet was defined solely on the score field. Two players with the same score were treated as the same element — the second insertion was silently ignored.
Fix
Changed the Comparator to include a tiebreaker: .thenComparing(Player::playerId). This made compare() return 0 only for truly identical players.
Key lesson
Comparator IS the equality definition in TreeSets and TreeMaps
Always add a tiebreaker unless you intentionally want duplicates suppressed
Test with deliberately equal sort keys to verify behavior
Production debug guideSymptom → Action guide for common ordered collection problems5 entries
Symptom · 01
ClassCastException on second insertion
→
Fix
Verify the element class implements Comparable, or that you passed a Comparator to the constructor. The error appears at runtime not compile time.
Symptom · 02
TreeSet/TreeMap has fewer elements than expected
→
Fix
Review your Comparator — compare(a,b) == 0 means duplicate. Use a tiebreaker like .thenComparing() to ensure equality only for genuine duplicates.
Symptom · 03
Contains() or get() returns unexpected results after mutation
→
Fix
Key objects that were changed after insertion break the tree structure. Use immutable keys or remove/re-insert after mutation.
Symptom · 04
NullPointerException on put/Add
→
Fix
TreeMap does not allow null keys. Check for null before inserting, or use a sentinel value. Null values are fine.
Symptom · 05
Iteration order is not sorted
→
Fix
TreeMap and TreeSet iterate in sorted order. If you see random order, you may have used a HashMap/HashSet by mistake. Check the collection type.
★ TreeMap/TreeSet Quick Debug Cheat SheetHands-on commands and fixes for the three most common production issues.
ClassCastException when adding second element−
Immediate action
Check if the class implements Comparable or if a Comparator was provided.
Commands
Add Comparable definition: class MyClass implements Comparable<MyClass> { ... }
Or pass a Comparator: new TreeSet<>(Comparator.comparing(MyClass::getField))
Fix now
If the class is third-party, wrap in a Comparator-based TreeSet immediately.
Silent data loss — fewer entries than expected+
Immediate action
List all keys being added and compare them using the Comparator logic.
Commands
Print the comparator: System.out.println(treeSet.comparator());
Check equals() vs compare(): add a temporary custom comparator that logs compare() calls.
Fix now
Add a secondary sort key: Comparator.comparing(MyClass::primary).thenComparing(MyClass::id)
Lookups returning wrong results after mutation+
Immediate action
Stop mutating key fields after insertion. Treat keys as immutable.
Commands
Consider using records: record Key(String name, int id) implements Comparable<Key> { ... }
If mutation is unavoidable, remove the key, mutate, then re-insert: treeSet.remove(key); key.mutate(); treeSet.add(key);
Fix now
Revert the mutation and create a new entry instead.
TreeMap vs HashMap — Feature Comparison
Feature / Aspect
TreeMap / TreeSet
HashMap / HashSet
Internal structure
Red-Black Tree
Hash Table (array of buckets)
Iteration order
Always sorted (natural or Comparator)
Unpredictable (effectively random)
get / contains
O(log n) worst case
O(1) average, O(n) worst case
put / add
O(log n) worst case
O(1) average, O(n) worst case
Range queries (subMap, headSet…)
Built-in, O(log n)
Not supported — requires full scan
Null keys allowed
No — throws NullPointerException
Yes (one null key for HashMap)
Null values allowed (Map only)
Yes
Yes
Thread safety
Not thread-safe
Not thread-safe
Memory overhead
Higher (tree node pointers)
Lower (flat array)
Best used when
Sorted iteration or range queries needed
Maximum speed, order doesn't matter
⚙ Quick Reference
8 commands from this guide
File
Command / Code
Purpose
LeaderboardTreeSet.java
public class LeaderboardTreeSet {
TreeSet
TaskSchedulerTreeMap.java
public class TaskSchedulerTreeMap {
TreeMap
NavigableApiDemo.java
public class NavigableApiDemo {
NavigableMap and NavigableSet
CustomComparatorDemo.java
public class CustomComparatorDemo {
Custom Ordering With Comparator
ChoosingTheRightCollection.java
public class ChoosingTheRightCollection {
TreeMap vs HashMap vs TreeSet vs HashSet
AdvancedNavigableMap.java
public class AdvancedNavigableMap {
Advanced NavigableMap Operations
TreeRotationSimulation.java
public class TreeRotationSimulation {
Red-Black Trees Under the Hood
NullKeyTrap.java
public class NullKeyTrap {
The NullPointerException Trap
Key takeaways
1
TreeMap and TreeSet use a Red-Black Tree internally, guaranteeing O(log n) for every insert, delete, and lookup
but also for range queries that HashMap/HashSet can't even do efficiently.
2
The Comparator you pass to a TreeMap or TreeSet is the equality definition
compare() returning 0 means 'same element', regardless of what equals() says. Always add a tiebreaker or you'll silently lose data.
3
The NavigableMap and NavigableSet APIs (floor, ceiling, headMap, tailSet, subMap) are the real reason to choose TreeMap/TreeSet. If you're not using at least one of those methods, you probably want a HashMap/HashSet instead.
4
Never mutate an object's fields that affect its sort order after inserting it as a key or element
the tree won't re-sort itself and the collection's integrity breaks silently with no exception thrown.
Common mistakes to avoid
3 patterns
×
Storing objects in TreeSet without Comparable or a Comparator
Symptom
You add a custom class like Employee to a TreeSet. On the second insertion you get a runtime ClassCastException: 'Employee cannot be cast to Comparable'. The first insert works because the tree is empty and no comparison is needed.
Fix
Implement Comparable<Employee> in your class, or supply a Comparator via the TreeSet constructor. The error appears at the second add() because that's when TreeSet first needs to compare two elements.
×
Comparator.compare(a,b) == 0 causes silent data loss
Symptom
A developer creates a TreeSet<Product> with a Comparator that sorts only by price. Two products with the same price but different names are treated as the same element; the second one is silently discarded. The set ends up with fewer entries than expected and no exception is thrown.
Fix
Always include a tiebreaker in your Comparator (e.g., .thenComparing(Product::name)) so that compare() returns 0 only when the objects are genuinely the same.
×
Mutating a key's fields after inserting it into a TreeMap or TreeSet
Symptom
TreeMap and TreeSet don't re-sort entries when an existing object's state changes. If you insert a Product keyed by price, then change that product's price field externally, the tree structure becomes corrupted — lookups may fail or return wrong results, and the collection's sorted invariant is silently broken.
Fix
Treat keys as immutable after insertion, or use immutable value types (records, Strings, primitives). If you must change a key, remove it, mutate it, then re-insert.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01SENIOR
How does TreeMap maintain its balance, and what is the time complexity o...
Q02SENIOR
Why does TreeMap throw NullPointerException for null keys while HashMap ...
Q03SENIOR
Explain the 'consistency with equals' requirement for Comparators used i...
Q04SENIOR
How does ConcurrentSkipListMap serve as a thread-safe alternative to Tre...
Q05JUNIOR
What happens if you call floorKey() and there is no key less than or equ...
Q01 of 05SENIOR
How does TreeMap maintain its balance, and what is the time complexity of a re-balancing operation?
ANSWER
TreeMap uses a Red-Black Tree, a self-balancing binary search tree. After every insert or delete, it performs at most O(log n) recoloring and rotation operations (at most two rotations for insertion, three for deletion). Each rotation is O(1), so the overall worst-case complexity for mutation remains O(log n). The tree guarantees O(log n) height, preventing worst-case O(n) degradation seen in plain BSTs.
Q02 of 05SENIOR
Why does TreeMap throw NullPointerException for null keys while HashMap allows one null key?
ANSWER
TreeMap requires all keys to be non-null because it invokes compareTo() or the Comparator's compare() on every key during insertion and lookup. Calling a method on null throws NPE. HashMap, on the other hand, uses hash codes and does not call equals() for the null key branch — it stores null in bucket 0 with a special check. If you need sorted order with null keys, you can write a custom Comparator that handles nulls (e.g., Comparator.nullsFirst()), but it's often simpler to use a sentinel value.
Q03 of 05SENIOR
Explain the 'consistency with equals' requirement for Comparators used in TreeSets. What happens if `compare(a, b) == 0` but `a.equals(b)` is false?
ANSWER
The documentation says that Comparators should be consistent with equals — meaning compare(a,b) == 0 iff a.equals(b). When they are inconsistent, the TreeSet will treat two objects as duplicates (compare returns 0) even though equals() says they are different. This leads to silent discarding of elements. The set's behavior is still deterministic, but it violates the Set contract (no duplicates by equals). In practice, using a Comparator without a tiebreaker is the most common source of this problem.
Q04 of 05SENIOR
How does ConcurrentSkipListMap serve as a thread-safe alternative to TreeMap, and what are its performance trade-offs?
ANSWER
ConcurrentSkipListMap implements ConcurrentNavigableMap using a skip list data structure. It provides thread-safe sorted map operations without external synchronization. Trade-offs: O(log n) average operations (similar to TreeMap), but higher memory overhead due to multiple levels of pointers. Under contention, it significantly outperforms a synchronized TreeMap because it uses lock-free techniques. It does not allow null keys, but allows null values. Use it when you need a thread-safe, sorted, concurrent navigable map.
Q05 of 05JUNIOR
What happens if you call floorKey() and there is no key less than or equal to the given key?
ANSWER
floorKey() returns null if no such key exists. The same applies to ceilingKey(), lowerKey(), and higherKey(). This is unlike firstKey() or lastKey(), which throw NoSuchElementException if the map is empty. Always check for null before using the result of floor/ceiling calls.
01
How does TreeMap maintain its balance, and what is the time complexity of a re-balancing operation?
SENIOR
02
Why does TreeMap throw NullPointerException for null keys while HashMap allows one null key?
SENIOR
03
Explain the 'consistency with equals' requirement for Comparators used in TreeSets. What happens if `compare(a, b) == 0` but `a.equals(b)` is false?
SENIOR
04
How does ConcurrentSkipListMap serve as a thread-safe alternative to TreeMap, and what are its performance trade-offs?
SENIOR
05
What happens if you call floorKey() and there is no key less than or equal to the given key?
JUNIOR
FAQ · 5 QUESTIONS
Frequently Asked Questions
01
Does TreeMap allow null keys in Java?
No. TreeMap throws a NullPointerException if you try to insert a null key, because it must call compareTo() or the Comparator on every key during insertion, and invoking a method on null causes the exception. Null values (not keys) are perfectly fine. If you need null key support with sorted iteration, you'd have to handle the null case manually in a custom Comparator — but it's usually cleaner to use a sentinel value instead.
Was this helpful?
02
Is TreeMap thread-safe?
No, TreeMap is not thread-safe. Concurrent reads and writes from multiple threads can corrupt the internal Red-Black Tree structure. Wrap it with Collections.synchronizedSortedMap(new TreeMap<>()) for basic synchronisation, or use ConcurrentSkipListMap from java.util.concurrent, which provides thread-safe sorted map operations with better concurrent throughput than a synchronised TreeMap.
Was this helpful?
03
What is the difference between floorKey() and lowerKey() in TreeMap?
floorKey(k) returns the greatest key that is less than or equal to k (inclusive). lowerKey(k) returns the greatest key strictly less than k (exclusive). Similarly, ceilingKey(k) is >= k, while higherKey(k) is strictly > k. Use floor/ceiling when the exact match is a valid result for your query.
Was this helpful?
04
Can I store duplicate keys in a TreeMap?
No. TreeMap implements Map, so each key maps to at most one value. If you put a key that already exists, the old value is replaced. TreeSet enforces uniqueness according to the Comparator or natural ordering. To store multiple values per key, use TreeMap<K, List<V>> or a multimap library like Guava.
Was this helpful?
05
What is the time complexity of constructing a TreeMap from an existing collection?
Adding N elements to a TreeMap via put() is O(n log n). If you have a pre-sorted list, you can use the constructor TreeMap(SortedMap<K,? extends V> m) which copies in linear time if the SortedMap uses the same ordering. Otherwise, there's no O(n) constructor for arbitrary sorted data — the tree must be built incrementally.