# Part 4.2 — HashMap Internal Implementation Deep Dive
This is the level of understanding expected for senior Java and JVM-focused interviews.
Typical questions include:
- Explain what happens inside
HashMap.put(). - Why does HashMap use powers of two?
- How does resizing work?
- What changed in Java 8?
- Why is resize expensive?
- What was the Java 7 infinite loop bug?
1. Internal Fields¶
Simplified HashMap source:
public class HashMap<K,V> {
transient Node<K,V>[] table;
transient int size;
int threshold;
final float loadFactor;
transient int modCount;
}
Let's understand each field.
table¶
Node<K,V>[] table;
This is the bucket array.
Example:
Index
0
1
2
3
4
5
6
7
↓
Node
↓
Node
↓
Node
Initially:
table = null;
The bucket array is not allocated until the first insertion (lazy initialization).
size¶
int size;
Number of key-value pairs.
Example:
map.put(1,"A");
map.put(2,"B");
size = 2
threshold¶
Resize limit.
Formula:
threshold = capacity × loadFactor
Example:
Capacity = 16
Load Factor = 0.75
Threshold = 12
The 13th insertion triggers a resize.
loadFactor¶
Default:
0.75f
One of the best trade-offs between memory usage and collision frequency.
modCount¶
Tracks structural modifications.
Used for fail-fast iterators.
Example:
map.put(1,"A");
modCount++
We'll revisit this when covering iterators.
2. Lazy Initialization¶
Interview question:
Does
new HashMap<>()allocate 16 buckets immediately?
Answer:
No.
Map<Integer,String> map = new HashMap<>();
Internally:
table = null
Only after:
map.put(1,"A");
does HashMap allocate:
capacity = 16
Why?
To avoid allocating memory for maps that remain empty.
3. The put() Algorithm¶
Suppose:
map.put("Apple",100);
Simplified flow:
put()
↓
hash()
↓
bucket index
↓
bucket empty?
↓
YES
↓
insert
↓
DONE
If not empty:
bucket
↓
compare keys
↓
existing?
↓
replace value
↓
else
↓
append/tree insert
4. hash() Method¶
Java 8 implementation:
static final int hash(Object key) {
int h;
return (key == null)
? 0
: (h = key.hashCode()) ^ (h >>> 16);
}
Interview question:
Why doesn't HashMap simply use
hashCode()?
Because some classes produce poor hash distributions.
Java mixes the high and low bits to improve bucket distribution.
Example:
Original
110101010101001011110000
↓
Shift
000000000000000011010101
↓
XOR
Better distribution
5. Bucket Index¶
Interview favorite.
Formula:
index = (capacity - 1) & hash;
Suppose:
Capacity = 16
capacity - 1 = 15
1111
Hash:
101011001
AND operation:
101011001
000001111
------------
000001001
=
9
Bucket:
9
Why Not %?¶
Instead of:
hash % capacity
Java uses:
hash & (capacity - 1)
Reasons:
- Faster bitwise operation.
- Works correctly because capacity is always a power of two.
6. Bucket Empty¶
Example:
Bucket 5
↓
empty
Insert node.
Bucket 5
↓
Apple
O(1)
7. Existing Bucket¶
Suppose:
Bucket
↓
Apple
↓
Orange
Insert:
Banana
HashMap:
- Traverses nodes
- Compares hashes
- Compares keys using
equals()
If found:
Replace value.
Otherwise:
Append node (or tree insert if treeified).
8. Resize Trigger¶
Default:
Capacity = 16
Threshold = 12
Insert 13th entry.
size = 13
↓
resize()
9. Resize Algorithm¶
Old table:
16 buckets
New table:
32 buckets
HashMap does not simply copy the old array.
Every node must be redistributed.
Old Bucket
↓
Node
↓
New Bucket
Each entry is relocated based on the new capacity.
10. Java 8 Resize Optimization¶
This is a commonly asked interview topic.
Suppose:
Old capacity:
16
New capacity:
32
For each entry, Java 8 checks one bit:
(hash & oldCapacity)
Only two possibilities exist:
Case 1¶
Bit = 0
Entry stays at the same index.
Old Bucket 5
↓
New Bucket 5
Case 2¶
Bit = 1
Entry moves.
Old Bucket 5
↓
New Bucket
5 + 16
=
21
No full hash recomputation is required.
This significantly improves resize performance.
11. Why Resize is Expensive¶
Suppose:
1 million entries
Resize:
Allocate larger array
↓
Visit every node
↓
Redistribute
↓
Update references
Complexity:
O(n)
Although put() is O(1) on average, the occasional resize incurs linear cost. This is why insertion is described as amortized O(1).
12. Choosing Initial Capacity¶
Interview scenario:
Expected entries:
1000
Bad:
new HashMap<>();
Several resizes will occur.
Better:
new HashMap<>(2048);
Why 2048?
We need:
1000 / 0.75
≈1334
Next power of two:
2048
This avoids costly resizes during population.
13. Java 7 Infinite Loop Bug¶
One of the classic interview questions.
Problem:
HashMap was never thread-safe.
Two threads resizing simultaneously could corrupt bucket links.
Result:
Node
↓
Node
↓
Node
↑
|
Loop
Traversal:
map.get(...)
could become:
A
↓
B
↓
C
↓
B
↓
C
↓
B
↓
Forever
CPU:
100%
Application hangs.
Why?¶
During resize, Java 7 reversed linked-list order while transferring nodes. Under concurrent modification, pointer updates could produce cycles.
Java 8 redesigned the transfer algorithm, reducing this specific failure mode—but HashMap is still not thread-safe.
14. HashMap Is Not Thread-Safe¶
Example:
Thread 1
map.put(1,"A");
Thread 2
map.put(2,"B");
Without synchronization:
- Lost updates
- Inconsistent internal state
- Visibility issues
Use:
ConcurrentHashMap
for concurrent access.
15. Memory Layout¶
HashMap
↓
table[]
↓
Bucket
↓
Node
↓
key
value
next
Node structure:
class Node<K,V>{
int hash;
K key;
V value;
Node<K,V> next;
}
If a bucket is treeified, nodes become TreeNode instances with additional fields like parent, left, right, prev, and a color bit for the red-black tree.
16. Time Complexity¶
| Operation | Average | Worst |
|---|---|---|
| put | O(1) | O(log n) |
| get | O(1) | O(log n) |
| remove | O(1) | O(log n) |
| resize | O(n) | O(n) |
17. Senior Interview Questions¶
Q1. Why is capacity always a power of two?¶
To allow efficient bucket index calculation using:
(hash & (capacity - 1))
and to distribute keys evenly.
Q2. Why does Java mix hash bits?¶
To reduce collisions caused by poor hashCode() implementations.
Q3. Why is resize expensive?¶
Every entry must be visited and redistributed into the new bucket array.
Q4. Why is insertion called amortized O(1)?¶
Most insertions are constant time. Only occasional resizes require O(n) work, so the average cost per insertion remains constant.
Q5. Can two different keys have the same bucket?¶
Yes.
This is a collision.
HashMap resolves collisions using linked lists or red-black trees.
Q6. Why doesn't HashMap use binary search?¶
Because entries are not globally sorted. HashMap is optimized for hashing-based lookup, not ordered search.
Common Mistakes¶
❌ "HashMap internally stores data in a linked list."
Correct:
It stores data in an array of buckets. Each bucket may contain:
- Nothing
- A linked list
- A red-black tree
❌ "HashMap lookup is always O(1)."
Correct:
Average:
O(1)
Worst case:
O(log n)
(or O(n) for non-treeified buckets or pathological cases).
❌ "HashMap is synchronized."
Correct:
It is not thread-safe.
Key Takeaways¶
HashMapis fundamentally an array-backed hash table.- Bucket index is computed with
(capacity - 1) & hash. - Hash spreading improves key distribution.
- Resizing doubles the capacity and redistributes entries.
- Java 8 optimizes resize by checking a single bit (
hash & oldCapacity). - Resizing is O(n), making insertion amortized O(1).
HashMapis not safe for concurrent modifications; useConcurrentHashMapwhen needed.- Understanding these internals is a hallmark of a strong senior Java candidate.
Next: Part 4.3 — 50+ HashMap Interview Questions and Scenario-Based Discussions¶
We'll cover:
- Tricky interviewer questions
- Real production debugging scenarios
putIfAbsent()vscomputeIfAbsent()- Why mutable keys break maps
- Memory leaks with maps
- Hash collision edge cases
- Coding and troubleshooting questions commonly asked in senior interviews.