Why HashMaps Switch to Trees at 8 Collisions
🎯 The Question
"In Java 8+, why does
HashMapconvert a bucket's linked list into a balanced Red-Black Tree (TreeNode) specifically at 8 collisions (TREEIFY_THRESHOLD = 8), and back to a list at 6 collisions? Why 8, and not 4 or 16?"
⚡ 30-Second Elevator Pitch
In classic hash tables, hash collisions are resolved via Separate Chaining (linked lists). If many keys hash to the same bucket, lookup time degrades from to .
Java 8 introduced Treeification: when a bucket reaches 8 elements (and table capacity is ), the bucket's linked list is transformed into a Red-Black Tree, guaranteeing worst-case search.
Why the Threshold is Exactly 8:
- The Math (Poisson Distribution): Under uniform, random hash codes, the probability of 8 keys landing in the same bucket by chance is less than 1 in 10 million (). Under normal conditions, trees are virtually never created.
- The Memory Penalty: A
TreeNodeis more than twice the memory size of a simpleListNode(it stores parent, left, right pointers, and a boolean color). Treeifying early would waste massive RAM. - Security Defense (Hash-Flooding DoS): If an attacker maliciously crafts thousands of colliding keys, the table cannot be forced into crawl; the tree bounds performance to .
🧠 Under-the-Hood: Linked List vs. Red-Black Tree in Bucket
🔬 The Poisson Distribution Proof
The Java JDK team documented the mathematical proof in the HashMap.java source code:
With a default load factor of , the probability of a bucket having collisions is:
- (Roughly 1 in 10,000,000)
Choosing 8 ensures treeification never occurs during honest program execution, only activating under pathological hash functions or malicious DoS attacks.
📌 Comparison Matrix: Node (Linked List) vs. TreeNode (Red-Black Tree)
| Dimension | HashMap.Node (Linked List) | HashMap.TreeNode (Red-Black Tree) |
|---|---|---|
| Search Time | linear scan | balanced search |
| Memory Footprint | Small (~24–32 bytes per node) | Large (~56–64 bytes per node) |
| Pointers Stored | 1 pointer (next) | 3 pointers (parent, left, right) + 1 boolean (red) |
| Insertion Overhead | Fast append | Requires tree rotations and color flips () |
| Untreeify Threshold | Reverts to list when size drops to 6 | Stays tree until bucket size shrinks below 6 |
💡 What Interviewers Ask Next (Follow-Up Traps)
-
"Why does the tree un-treeify at 6 elements instead of 8?"
- Answer: To prevent Hysteresis (Thrashing). If treeify and untreeify both triggered at 8, repeatedly inserting and deleting a single element around the threshold would force the HashMap to continuously construct and tear down Red-Black Trees, cratering performance. The gap between 8 and 6 provides a stability buffer.
-
"What is the second condition required before a bucket treeifies?"
- Answer:
MIN_TREEIFY_CAPACITY = 64. Even if a bucket reaches 8 collisions, if the entire table has fewer than 64 buckets, Java chooses to resize (double) the table instead of treeifying. Doubling the array redistributes the colliding keys across new buckets with less overhead.
- Answer:
Interview Answer: Java's HashMap converts buckets to Red-Black Trees at 8 collisions because Poisson distribution shows the odds of 8 collisions under a good hash are 1 in 10 million. TreeNodes consume over double the memory of ListNodes, so the threshold of 8 keeps memory overhead low while bounding worst-case lookup time to to neutralize Hash-DoS attacks.