Hook: Interviewers love this question because it tests whether you know the difference between average speed and worst-case speed in a real Java collection.
Question: Why did Java 8 HashMap switch some collision buckets from linked lists to red-black trees?
Answer: Because a linked list gets slower as more keys collide into the same bucket: searching can become O(n). Java 8 upgrades a “hot” bucket to a red-black tree so the worst-case lookup, insert, and delete in that bucket become O(log n). This keeps HashMap fast even when many keys share the same hash code.
Interview-Ready Answer: In Java 8, HashMap still gives average O(1) performance, but if many keys land in the same bucket, that bucket can become a linked list that degrades to O(n). To protect against bad hash codes and collision attacks, Java 8 treeifies large buckets into a red-black tree, which keeps worst-case bucket operations at O(log n). They chose red-black trees because they are a practical balance: fewer rotations and good real-world performance compared with stricter trees like AVL.
TREEIFY_THRESHOLD = 8, UNTREEIFY_THRESHOLD = 6, and MIN_TREEIFY_CAPACITY = 64.O(log n) instead of walking the whole list.A red-black tree is a self-balancing binary search tree that keeps its height small by enforcing color rules after insertions and deletions. “Self-balancing” means the tree automatically reorganizes itself so it does not become a long chain.
Java did not choose AVL trees here because AVL trees are more strictly balanced, which usually means more rotations on updates. HashMap’s collision bins need a structure that is fast in practice for both reads and writes, not the mathematically strictest tree.
Memory hook: think of a bucket as a hallway of people waiting to be checked. A linked list is a single-file line; a red-black tree is a sorting desk that lets you jump left or right instead of asking every person one by one.
Because trees are more expensive than lists when buckets are small. Most HashMap buckets are tiny or empty, so using trees all the time would waste memory and add update overhead for no benefit. Java 8 keeps the common case lightweight and only pays tree cost when collisions become suspiciously high.
| Structure | Lookup in bucket | Update cost | Why it matters |
|---|---|---|---|
| Linked list | O(n) | Cheap | Best for tiny buckets |
| AVL tree | O(log n) | More rotations | Very strict balance |
| Red-black tree | O(log n) | Fewer rotations | Best practical trade-off |
O(1) with a decent hash function.O(n) behavior in one bucket.O(log n).0.75. This controls when the whole table resizes, not when treeification happens.compareTo), the tree can order them directly.equals, not from sorting order.TreeMap is for.Bottom line: Java 8 uses red-black trees to protect HashMap from pathological collision cases while keeping the normal case fast, small, and simple.
Real-World Story: Imagine a checkout service for an e-commerce site. User cart IDs are converted into keys for an in-memory cache backed by HashMap. One day, a bad client library or a malicious request pattern creates lots of keys with the same hash code, so many carts land in the same bucket.
At first everything looks fine, because HashMap still works. But as the bucket gets longer, every get and put has to walk more entries. Latency rises, then p95 and p99 response times jump, and the checkout API starts timing out under load.
What goes wrong in production:
With Java 8 tree bins, that same collision bucket is converted into a red-black tree once it becomes large enough, so the bad bucket stops behaving like a slow line and starts behaving like a balanced search structure. The outage may still hurt if the hash function is terrible, but the collapse is much less severe than in older HashMap behavior.
import java.util.HashMap;
import java.util.Map;
public class Main {
// A key with a deliberately terrible hash function.
// Every instance returns the same hash code, so all keys collide into one bucket.
static final class ConstantHashKey implements Comparable<ConstantHashKey> {
private final int id;
ConstantHashKey(int id) {
this.id = id;
}
@Override
public int hashCode() {
return 42; // Forces collisions so we can demonstrate why bucket treeification matters.
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof ConstantHashKey)) return false;
ConstantHashKey other = (ConstantHashKey) obj;
return this.id == other.id;
}
@Override
public int compareTo(ConstantHashKey other) {
// Helps the tree bucket order keys consistently when many collisions happen.
return Integer.compare(this.id, other.id);
}
@Override
public String toString() {
return "K" + id;
}
}
public static void main(String[] args) {
Map<ConstantHashKey, String> map = new HashMap<>();
// Insert enough colliding keys to make the bucket very crowded.
// In Java 8+, HashMap may treeify such a bucket once the table is large enough.
for (int i = 1; i <= 20; i++) {
map.put(new ConstantHashKey(i), "value-" + i);
}
System.out.println("Map size: " + map.size());
// Successful lookup: even with heavy collisions, HashMap still retrieves the right value.
System.out.println("Lookup K7 -> " + map.get(new ConstantHashKey(7)));
// Edge case / failure path: a key that was never inserted returns null.
// The same hash code is not enough; equals() must also match.
System.out.println("Lookup K99 -> " + map.get(new ConstantHashKey(99)));
// Another useful check: containsKey uses the same equality logic as get.
System.out.println("Contains K15? " + map.containsKey(new ConstantHashKey(15)));
System.out.println("Contains K0? " + map.containsKey(new ConstantHashKey(0)));
// Show that a null key is still supported by HashMap.
// This is unrelated to tree bins, but it is a common interview edge case.
map.put(null, "null-value");
System.out.println("Lookup null -> " + map.get(null));
}
}
Follow-up & Tricky Questions:
equals and hashCode.O(1); only a collision-heavy bucket may fall back to O(log n).equals decides whether they are truly the same key.hashCode still hurt performance in Java 8? Yes. Tree bins reduce the damage, but a very poor hash function still causes extra collisions and extra work compared with a well-distributed hash.Common Mistakes:
O(1). Correction: It is average O(1), but collision-heavy buckets can become O(log n) in Java 8 or O(n) before treeification.Memory Hook: “List for little, tree for trouble” — small collision buckets stay as a simple line, but a crowded bucket turns into a balanced tree to stop worst-case slowdown.
Cheat Sheet:
O(1) lookups.Practice Tasks:
hashCode and observe how HashMap still stores and retrieves values correctly.