DevNotes
Support
Collection Framework · Chapter 22

HashMap Internals

The full HashMap chain end to end — hash spreading, bucket math, collisions vs duplicates, treeification rules, resize bit-split, modCount and 50+ interview answers.

◷ 30 min read§ 24 sections? 49 interview questions{ } 28 code examples

#Introduction

This is one of the highest-value Java Collections chapters for interviews. If an interviewer asks "Explain how HashMap works internally," you should be able to walk through this entire chain:

put(key, value)
hashCode()
hash spreading
bucket index
Empty bucket?
insert
collision → equals() comparison
equals() comparison
Same key?
replace
add node
Too many collisions?
treeification
keep list

Then understand: size → threshold exceeded → resize → capacity doubles → entries redistributed.

Let's break this down deeply without repeating concepts already covered in previous chapters.

#What Is HashMap?

HashMap<K,V> is a hash-table-based implementation of Map.

Java
Map<String, Integer> map = new HashMap<>();

It provides average get(), put() and remove() in O(1), assuming good hash distribution. The important word is average — poor hashing or extreme collisions can change the performance characteristics.

#Internal Structure

Bucket 0
empty
Bucket 1
Node
Bucket 2
empty
Bucket 3
NodeNode
Bucket 4
empty
Node<K,V>[] table

Each bucket can contain nothing, linked nodes, or — when collision conditions warrant — a tree bin.

Node Structure

Java
static class Node<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
}

So each entry essentially stores hash, key, value, next. The actual JDK implementation contains additional details, but this is the important interview model.

#The put() Process

Java
map.put("Java", 100);
"Java"
hashCode()
hash spreading
bucket index
inspect bucket
insert/update
The two methods/concepts interviewers want you to understand: hashCode() and equals()

Step 1 — hashCode()

For the key "Java", Java first obtains key.hashCode(), which produces an integer. For a custom object:

Java
class Employee {
    int id;

    @Override
    public int hashCode() {
        return id;
    }
}

HashMap uses that hash information to determine where the entry should go.

hashCode() Does NOT Directly Give the Bucket

A common interview mistake is saying "HashMap uses hashCode as the bucket index." Not exactly. hashCode() returns an integer that can be any int value. HashMap needs to transform that value into a valid table index, and modern HashMap also performs a hash-spreading step.

Hash Spreading

Conceptually, Java 8+ HashMap uses h ^ (h >>> 16) where h = key.hashCode().

original hash
upper bits mixed into lower bits
spread hash

Why? Because bucket selection relies heavily on the lower bits when the table size is a power of two. Mixing higher bits into lower bits helps distribute keys more effectively.

Why Mix the Upper Bits?

Suppose two keys have hash codes where the lower bits are identical but the upper bits differ. If only the lower bits influenced bucket selection, those differences would be wasted. h ^ (h >>> 16) mixes information from the high bits into the low bits, improving distribution.

Bucket Index Calculation

One of the most frequently asked HashMap questions. For a table length n:

Java
int index = (n - 1) & hash;

Why Not %?

You might expect hash % n. HashMap instead uses hash & (n - 1) when n is a power of two.

ValueBinary
n = 1610000
n − 1 = 1501111

Therefore hash & 01111 extracts the relevant lower bits. Bitwise masking is efficient and works cleanly because HashMap maintains power-of-two capacities.

Why Must Capacity Be a Power of Two?

A classic interview question. If capacity = 16, then capacity − 1 = 15 = 00001111. Masking becomes straightforward — hash & 00001111 efficiently maps hashes to indices 0 → 15. A non-power-of-two capacity would not give the same useful bitmask behaviour.

Complete Hash Calculation

key
key.hashCode()
h ^ (h >>> 16)
(n - 1) & hash
bucket index
This sequence is extremely important

Example

Suppose hash = 101101011010 and capacity = 16. Then n − 1 = 15 = 1111, and the final bucket is determined from the relevant lower bits through hash & 1111. The exact numeric result isn't important — the mechanism is.

Empty Bucket

Suppose index = 5 and table[5] == null. HashMap simply inserts table[5] → Node("Java", 100). Done.

Occupied Bucket

Then we have a hash collision — e.g. Key A → bucket 5 and Key B → bucket 5. Even if A != B, they can still map to the same bucket. This is completely normal.

#Collisions vs Duplicate Keys

Hash Collision

A collision means different keys → same bucket. It does not necessarily mean hashCode(A) == hashCode(B). Two different hash codes can still map to the same bucket, because the final bucket index uses only part of the hash.

Collision Example

Key A with hash 10 and Key B with hash 26: with a small table (16 buckets), 10 & 15 = 10 and 26 & 15 = 10, so both map to the same bucket. HashMap stores both:

Bucket 9
empty
Bucket 10
AB
Bucket 11
empty

How Does HashMap Distinguish Colliding Keys?

It uses hash + equals(): same bucket → compare stored hash → if necessary compare keys with equals(). The equals() method determines whether the key is actually the same logical key.

Collision ≠ Duplicate Key

This distinction is critical.

CollisionDuplicate key
Different keysSame logical key
Same bucketSame bucket
equals() == falseequals() == true
Bucket holds multiple entriesExisting value is replaced

Example

Java
map.put("A", 10);
map.put("B", 20);

If A and B collide, the bucket holds A → B — both remain. But after map.put("A", 30); the map holds A → 30, B → 20: the existing A mapping is updated.

#The equals() / hashCode() Contract

If a.equals(b) is true, a.hashCode() must equal b.hashCode().

Otherwise HashMap can place logically equal keys into different buckets.

Example of a Broken Contract

Java
class Employee {
    int id;

    @Override
    public boolean equals(Object o) {
        Employee e = (Employee) o;
        return id == e.id;
    }

    @Override
    public int hashCode() {
        return System.identityHashCode(this);
    }
}

Now e1.equals(e2) → true, but e1.hashCode() != e2.hashCode() can occur. HashMap's assumptions are broken.

Correct Contract

If a.equals(b) == true, then a.hashCode() == b.hashCode() must also be true. But the reverse is NOT required — an extremely common interview question.

Same Hash ≠ Equal Objects

Two objects can have the same hashCode() but equals() == false. That's a collision: A.hashCode() → 100, B.hashCode() → 100, A.equals(B) → false — HashMap stores both.

Roles of hashCode() and equals()

calculate hash
find bucket
compare candidate hash
compare keys with equals()
Same key?
found
next candidate
When looking for a key

#get() and remove() Internals

get()

Java
map.get(key);
key
hashCode()
spread hash
bucket index
compare candidate
equals()
return value
If no matching key exists: null

remove()

Java
map.remove(key);
hash
bucket
search
equals()
remove matching node
If the key doesn't exist, no mapping is removed

#Java 7 vs Java 8 and Treeification

Java 7 vs Java 8 HashMap

A very important interview topic.

Java 7
bucket
Node → Node → Nodelinked list only
Java 8+
bucket
heavy collision chain
TreeBin → Red-Black Tree

This is one of the major improvements.

Why Treeification Was Introduced

A heavily collided bucket A → B → C → D → E → F → G → H → … makes searching approach O(n). Treeification changes the structure to a balanced tree, so lookup becomes approximately O(log n) within that tree.

  • D
    • B
      • A
      • C
    • F
      • E
      • G

Red-Black Tree

The tree structure used for treeified HashMap bins is a self-balancing Red-Black Tree. The goal is to prevent the collision structure from degenerating into a long linear chain.

Treeification Threshold

Java
TREEIFY_THRESHOLD = 8

Treeification is considered when a bin becomes sufficiently populated. But HashMap doesn't immediately treeify every bin that reaches eight nodes.

Minimum Treeify Capacity

Java
MIN_TREEIFY_CAPACITY = 64
bin gets crowded
Table capacity < 64?
resize instead of treeify
treeify
Once the table is large enough, treeification can occur when the collision threshold is reached

Why Resize Instead of Treeify?

A small table naturally causes more collisions. With capacity = 16 and one bucket containing many entries, rather than immediately building a tree, HashMap may prefer to resize to 32 or 64, spreading entries across more buckets.

Correct Interview Answer

Untreeification

A tree bin can later be converted back to a linked structure when the number of nodes becomes sufficiently small. A commonly cited implementation threshold is:

Java
UNTREEIFY_THRESHOLD = 6

The exact transition conditions are implementation details, but the concept is: many collisions → tree; entries decrease → potentially linked list.

Why Not Keep the Tree Forever?

A tree has more structural overhead than a simple linked node chain. If the bin becomes small again, the linked representation may be more efficient, so HashMap can reduce unnecessary overhead.

Treeification Is Per Bucket

HashMap doesn't become "entire Map → Red-Black Tree." Instead:

BucketStructure
1Linked list
2Empty
3Tree
4Linked list

Only collision-heavy bins are treeified.

HashMap Is Still a Hash Table

Even with tree bins, HashMap is an array of buckets; some buckets may contain trees. It doesn't become a TreeMap.

HashMapTreeMap
Hash tableRed-Black Tree
O(1) average lookupO(log n)

HashMap's treeification is only a mechanism for handling collisions inside individual buckets.

#Load Factor, Capacity, Size and Threshold

Load Factor

Load factor controls when HashMap resizes.

Default load factor: 0.75.

Text
threshold = capacity × loadFactor
          = 16 × 0.75
          = 12

Once the size crosses the threshold, resizing occurs.

Why 0.75?

A compromise between memory usage and collision frequency.

Load factorBucketsCollisionsMemory
LowerMoreFewerMore
HigherFewerMoreLess

Capacity vs Size vs Threshold

TermMeaningExample
CapacityNumber of buckets16
SizeNumber of mappings10
ThresholdApproximately when resizing is triggered: capacity × load factor12

With capacity 16 and size 10, the Map has 16 buckets but only 10 entries.

Load Factor Trade-Off

  • Lower load factor (e.g. 0.50): more buckets → fewer collisions → more memory.
  • Higher load factor (e.g. 0.90): fewer buckets → potentially more collisions → less table memory.

The default 0.75 is a practical balance.

Does a Higher Load Factor Always Mean Faster?

No. A higher load factor can increase collisions, leading to longer bins, more comparisons and potentially more tree bins. The correct load factor depends on workload and memory/performance trade-offs.

Does a Lower Load Factor Always Mean Faster?

No. You consume more memory and may get diminishing returns. Performance and memory must be balanced.

#Resizing

Resize

When size > threshold, HashMap grows. For the normal growth path: 16 → 32 → 64 → 128. Capacity generally doubles.

Why Double the Capacity?

Doubling allows HashMap to efficiently redistribute entries. Because capacity remains a power of two, the new bucket position can be determined efficiently — an important Java 8+ implementation optimization.

Rehashing

The term "rehashing" is often used loosely. Strictly speaking, when HashMap resizes, it doesn't necessarily recompute each key's original hashCode() from scratch. It redistributes entries based on the existing hash information and the new table capacity.

The Important Resize Trick

With old capacity 16 and new capacity 32, an existing entry's new index is either its old index or old index + old capacity, depending on a particular hash bit.

old bucket 5
Hash bit 16 set?
new bucket 5
new bucket 21 (5 + 16)
One of the clever optimizations in Java 8 HashMap resizing

Why Only One Bit Matters During Doubling

Because new capacity = old capacity × 2, the mask gains one additional bit. Each existing entry can generally remain at the same index or move to old index + old capacity, depending on that bit. For example, an entry in bucket 3 of a 16-bucket table goes to bucket 3 or 19 (3 + 16) after resizing to 32. This avoids recalculating the complete bucket placement from scratch.

Resize Visualization

Bucket 3
AB
Bucket 7
C
Bucket 12
DE
Before — capacity 16
Bucket 3
A
Bucket 7
C
Bucket 12
D
Bucket 19
B
Bucket 28
E
After — capacity 32 (exact distribution depends on hash bits)

#Construction and Lazy Initialization

Initial Capacity

new HashMap<>(16); specifies an initial-capacity-related sizing expectation, not necessarily "allocate exactly 16 buckets immediately." Modern HashMap is lazily initialized — the actual table allocation happens when needed, typically on the first insertion.

Lazy Initialization

Java
HashMap<String, Integer> map = new HashMap<>();

Creating the HashMap does not necessarily allocate the full bucket table immediately. The table is initialized when the Map first needs storage.

Default Capacity and Threshold

The commonly documented default initial capacity is 16 with load factor 0.75, and the table is lazily initialized. Once the table is initialized, the resize threshold becomes approximately 12.

Constructor Options

Java
new HashMap<>();
new HashMap<>(32);
new HashMap<>(32, 0.75f);
new HashMap<>(existingMap);

Choosing Initial Capacity

If you know approximately how many entries will be inserted, supplying a suitable initial capacity can reduce resizing:

Java
Map<String, User> users = new HashMap<>(expectedCapacity);

But be careful: constructor capacity is not necessarily equal to the eventual internal table length at construction time. HashMap rounds sizing to its power-of-two strategy.

#put() — Complete Flow

Combine everything for map.put(key, value):

1. Is the table initialized?
2. Calculate / spread hash
3. Calculate bucket index
4. Is bucket empty?
insert
5. compare existing key
5. Compare existing key
6. Same key?
replace value
collision — traverse linked/tree bin, insert new node
Collision threshold reached? → treeify if eligible
size > threshold? → resize
This is the answer interviewers are looking for

Duplicate Key During put()

Java
map.put("A", 10);
map.put("A", 20);

HashMap finds same hash + keys equal, so A → 20. Size remains 1.

New Key During Collision

If A and B land in the same bucket but A.equals(B) == false, the bucket holds A → B and the size increases.

get() Does Not Modify the Map

A normal map.get(key) does not add an entry. It simply: hash → bucket → search → return.

#Null Handling

get() and Null Values

HashMap permits null values, so map.get("A") returning null could mean A is absent or A exists with a null value. Use map.containsKey("A") to distinguish.

Null Key Internals

HashMap allows one null key: map.put(null, "value"); works. Internally, the null key is handled specially rather than calling null.hashCode(), which would cause a NullPointerException.

Null Value

HashMap also allows map.put("A", null);. So: null key → one allowed, null value → allowed. This differs from ConcurrentHashMap.

#The Mutable Key Problem

One of the most important HashMap interview topics.

Java
class Employee {
    int id;

    @Override
    public int hashCode() {
        return id;
    }

    @Override
    public boolean equals(Object obj) {
        // ...
    }
}
Java
Employee e = new Employee(10);
map.put(e, "John");

e.id = 20;

The object remains physically in its old bucket, but its logical hash may now point to a different bucket.

Initially
Employee id = 10
hash = 10
bucket 10entry stored here
After mutation
Employee id = 20
hash = 20
bucket 20get(e) searches here — not found

Why Does get() Fail?

Because HashMap calculates the current hash of e (20) and searches bucket 20, but the entry was originally stored in bucket 10. Therefore it isn't found.

Best Practice

Use immutable keys. Good examples: String, Integer, Long, enums, records with immutable components, or carefully designed immutable domain objects.

equals() Must Be Consistent

If key equality changes after insertion, Map behaviour can become unpredictable. Keys should ideally be immutable + stable equals() + stable hashCode() while stored.

Why String Is an Excellent HashMap Key

String is effectively immutable. Its equals() and hashCode() behaviour remains stable after insertion — one reason Strings are commonly used as keys.

#Concurrency and Iteration

Java 7 HashMap Infinite Loop Issue

A historical interview topic: why could Java 7 HashMap experience an infinite loop during concurrent resize? The old implementation could transfer linked-list nodes in a way that allowed concurrent resize operations to create a cyclic linked structure — A → B → C could become A → B → C → A — so traversal could loop forever.

Java 8 Improvement

Java 8 changed the resize mechanics and introduced tree bins. The historical infinite-loop problem associated with concurrent HashMap resizing was addressed by the changed implementation.

HashMap Concurrent Modification

Even though modern HashMap is improved internally, it still should not be concurrently mutated by multiple threads without external synchronization. Use ConcurrentHashMap for concurrent Map requirements.

Fail-Fast Iterator

HashMap iterators are generally fail-fast:

Java
for (String key : map.keySet()) {
    map.put("X", 100);
}
Output
ConcurrentModificationException

What Does Fail-Fast Mean?

The iterator detects certain structural modifications made outside the iterator and may throw ConcurrentModificationException. The purpose is to detect programming errors early. It is not a thread-safety mechanism.

modCount

HashMap internally maintains a modification count. Iterators compare their expectedModCount against the Map's current modCount. If they differ unexpectedly, ConcurrentModificationException may be thrown.

Fail-Fast Is Best-Effort

Fail-fast behaviour is not guaranteed as a synchronization mechanism — the JDK documentation describes it as best-effort. Never write business logic that depends on ConcurrentModificationException being thrown.

Iterator Removal

Java
Iterator<String> iterator = map.keySet().iterator();

// ... when you want to remove the current entry:
iterator.remove();

Use iterator.remove() rather than directly modifying the Map — the supported iterator-removal mechanism.

Structural vs Non-Structural Modification

A structural modification changes the Map's structure — adding or removing a mapping. Simply replacing the value of an existing mapping isn't necessarily structural, so map.put(existingKey, newValue) may not have the same iterator implications as adding/removing an entry.

HashMap Is Not Synchronized

HashMap is not thread-safe. If multiple threads modify it concurrently, use ConcurrentHashMap or another appropriate synchronization strategy.

Collections.synchronizedMap()

Java
Map<K,V> map = Collections.synchronizedMap(new HashMap<>());

This provides synchronized access around Map operations, but iteration requires proper external synchronization according to the wrapper's contract. For high-concurrency applications, ConcurrentHashMap is usually the better fit.

#Tree Bins and Comparable

Treeification and Comparable

Tree bins need a way to order nodes. When keys are comparable in a suitable way, their ordering can help the tree structure. If keys aren't naturally comparable, HashMap has additional tie-breaking mechanisms.

Do Keys Need Comparable?

No — a common misconception. You can use HashMap<MyClass, String> without MyClass implements Comparable<MyClass>. Treeification can still occur, using internal tie-breaking logic.

Why Doesn't HashMap Require Comparable?

Because HashMap isn't a sorted collection. The tree is an internal collision-management mechanism, not a user-visible sorting feature.

HashMap Tree Bin ≠ Sorted Map

This is extremely important. If you need sorted keys, use TreeMap. Tree bins exist only to manage collisions efficiently.

#Complexity and Hash Quality

Worst-Case Complexity

SituationComplexity
Heavily collided linked bin (before treeification)O(n)
Tree bin lookup≈ O(log n)
Normal operations with good hashingO(1) average

Does Treeification Make HashMap O(log n)?

No — another interview trap. HashMap is still average O(1). The tree only affects individual heavily collided bins. You should not describe HashMap as O(log n) just because tree bins exist.

Why HashMap Needs Both Hash and Equals

Imagine 100,000 entries. If HashMap used only equals(), it might have to compare the requested key against every entry. Hashing narrows the search: 100,000 entries → one bucket → few candidates, then equals() identifies the exact key.

  • hashCode() → narrow the search space
  • equals() → confirm the exact key

Performance Depends on a Good hashCode()

Java
@Override
public int hashCode() {
    return 1;
}

Every key maps to the same bucket (A → B → C → D → E → …), creating heavy collision behaviour. Modern HashMap can treeify such bins when conditions permit, but a good hash function is still essential.

Constant Hash Code Example

Java
class BadKey {
    int id;

    @Override
    public int hashCode() {
        return 1;
    }

    @Override
    public boolean equals(Object obj) {
        return this == obj;
    }
}

Every key goes to the same bucket — terrible for distribution.

HashMap Doesn't Guarantee Order

Java
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);

The iteration order is not guaranteed to be A, B, C. Use LinkedHashMap for insertion/access order, and TreeMap for sorted order.

HashMap Memory Model

HashMap
Node[] table
buckets0: — · 1: Node · 2: — · 3: Node → Node · …
As entries grow: size → threshold → resize → larger table

#Complete Interview Answers

#Interview Questions

Q1Why does HashMap use a power-of-two capacity?

So bucket indexing can efficiently use (n - 1) & hash, and doubling the capacity during resize allows entries to either remain at the same index or move by the old capacity based on one additional hash bit.

Q2What is the difference between capacity, size and threshold?
  • Capacity → number of buckets
  • Size → number of mappings
  • Threshold → approximate size at which resize occurs (≈ capacity × load factor)
Q3What is the default load factor?

0.75.

Q4What is the default initial capacity?

Commonly 16, with lazy table initialization.

Q5Why isn't a collision a problem?

Collisions are expected in hash tables. HashMap handles them through linked nodes and, for sufficiently collision-heavy bins under the right conditions, Red-Black Trees. The goal is to keep average performance efficient.

Q6What happens when two keys have the same hashCode?

They may map to the same bucket. HashMap then uses key equality to determine whether they represent the same logical key. If equals() == false, both entries can coexist.

Q7What happens if two keys have the same hashCode and equals() returns true?

They represent the same logical key. The new value replaces the existing value, and the size does not increase.

Q8Can two unequal keys have the same hashCode?

Yes — that's a collision. hashCode(A) == hashCode(B) with A.equals(B) == false is perfectly valid.

Q9If two objects have the same hashCode, are they equal?

No. The correct implication is: equals() == true ⇒ hashCode() must be the same. But the same hashCode() does not imply equals() is true.

Q10Why should HashMap keys be immutable?

Because changing fields used by equals() or hashCode() after insertion can cause the Map to search a different bucket from the one where the entry was stored.

Q11Can HashMap have a null key?

Yes — one null key.

Q12Can HashMap have multiple null values?

Yes — e.g. map.put("A", null); map.put("B", null); are both valid.

Q13Is HashMap thread-safe?

No. Concurrent mutation requires external synchronization or a concurrent collection such as ConcurrentHashMap.

Q14Why doesn't HashMap use hashCode() directly as the index?

Because hashCode is a general integer and isn't necessarily in the range 0 … capacity − 1. HashMap spreads the hash and maps it to a valid bucket index.

Q15Why does HashMap use (n - 1) & hash?

Because its table size is maintained as a power of two, allowing efficient bit masking to calculate the bucket index.

Q16What is treeification?

Converting a collision-heavy bucket from a linked-node representation into a tree-based representation.

Q17Is every bucket a tree?

No. Only sufficiently collision-heavy bins can become trees. Most bins remain empty, a single node, or linked nodes.

Q18Is HashMap internally a Red-Black Tree?

No. It is primarily a hash table (Node[]), with individual collision-heavy bins potentially represented as Red-Black Trees.

Q19Does treeification make HashMap sorted?

No. Tree bins are internal collision-management structures. HashMap still doesn't guarantee sorted iteration.

Q20What is TREEIFY_THRESHOLD?

A common JDK implementation constant: 8. It represents the bin size at which treeification may be considered — but MIN_TREEIFY_CAPACITY = 64 also matters.

Q21What is UNTREEIFY_THRESHOLD?

A common implementation value: 6. It is associated with converting a tree bin back to a linked representation when the bin becomes sufficiently small.

Q22Why does HashMap resize before treeifying in some cases?

If the table is too small, collisions may be caused primarily by insufficient capacity. Increasing capacity can distribute entries across more buckets more cheaply than immediately creating a tree.

Q23Does HashMap recompute hashCode during resize?

Don't say "It calls hashCode() again for every key." Better: the existing hash values are reused and entries are redistributed according to the new capacity. Java 8+ uses an efficient high-bit split during doubling.

Q24Why does resizing normally double capacity?

It preserves the power-of-two property and enables efficient redistribution using the old index and the old capacity offset.

Q25What is the worst-case lookup complexity?

O(n) for a heavily collided linked bin; O(log n) for a treeified bin; O(1) average with good hashing.

Q26What is the difference between HashMap and ConcurrentHashMap?
HashMapConcurrentHashMap
Not thread-safeThread-safe
Permits null key/valueNo null key/value
Fail-fast iteratorWeakly consistent iterator
General useDesigned for concurrent access
Q27What happens if HashMap is modified while iterating?

A fail-fast iterator may throw ConcurrentModificationException for certain structural modifications. But this behaviour is best-effort, not a concurrency guarantee.

#Coding Questions

Q1Duplicate key
Java
Map<String, Integer> map = new HashMap<>();

map.put("A", 10);
map.put("A", 20);

System.out.println(map.size());
System.out.println(map.get("A"));
Output
1
20
Q2Null value present
Java
Map<String, Integer> map = new HashMap<>();

map.put("A", null);

System.out.println(map.get("A"));
System.out.println(map.containsKey("A"));
Output
null
true
Q3Key absent
Java
Map<String, Integer> map = new HashMap<>();

System.out.println(map.get("A"));
System.out.println(map.containsKey("A"));
Output
null
false

This demonstrates the null-value ambiguity.

Q4Same hash, not equal

A.hashCode() = 100, B.hashCode() = 100, A.equals(B) = false. Can both exist in HashMap? Yes — they are different logical keys despite having the same hash.

Q5Equal but different hash

A.hashCode() = 100, B.hashCode() = 200, A.equals(B) = true. Is this a valid key implementation? No — it violates equal objects → same hashCode.

Q6Mutable key
Java
Employee e = new Employee(10);

map.put(e, "John");
e.setId(20);

System.out.println(map.get(e));

Can this return null? Yes, if id participates in hashCode() and equals(). The classic mutable-key problem.

Q7Constant hashCode

If all keys return hashCode() = 1, all keys tend toward the same bucket. Modern HashMap can treeify the heavily collided bin when its capacity and threshold conditions permit, but performance is still much worse than having a well-distributed hash function.

Q8A bin reaches 8 entries

Does it automatically become a tree? Not necessarily. If the table capacity is below the required minimum of 64, HashMap may resize instead.

Q9Threshold calculation

With capacity = 16 and load factor = 0.75, the threshold is approximately 16 × 0.75 = 12. When the size exceeds the threshold, a resize is triggered.

Q10Resize index

Old capacity 16 → new capacity 32. An entry from bucket 5 can generally remain at 5 or move to 21 (5 + 16).

#Senior-Level Questions

Q1Why does HashMap use hash spreading if hashCode is already provided?

hashCode implementations may not distribute useful information evenly in the low bits. Because bucket selection with power-of-two capacity depends heavily on low bits, HashMap mixes higher bits into lower bits to improve distribution.

Q2Why is equals() called after locating a bucket?

Because multiple keys can map to the same bucket. Hashing narrows the search from all entries → one bucket; equality then determines which key exactly.

Q3Why can equal objects not be placed in different buckets?

They shouldn't be, assuming the equals()/hashCode() contract is correctly implemented. If a.equals(b), then a.hashCode() == b.hashCode(), and they follow the same hash/bucket calculation.

Q4Why can unequal objects be in the same bucket?

Because a finite number of buckets must accommodate potentially many possible hash values. Collisions are inevitable.

Q5Why doesn't HashMap simply use a LinkedList for everything?

Because a long collision chain can degrade lookup toward O(n). Treeification provides better worst-case behaviour for heavily collided bins.

Q6Why doesn't HashMap use a tree for the entire Map?

Because hashing gives excellent average O(1) lookup. A global tree would unnecessarily turn normal lookups into O(log n). The tree is only needed for problematic individual bins.

Q7Why doesn't HashMap automatically resize after every collision?

Because resizing costs time and memory, and a few collisions are normal. HashMap balances table size + collision rate + memory using capacity and load factor.

Q8What happens if the load factor is 1.0?

The Map can tolerate a fuller table before resizing. This may reduce memory usage but can increase collisions. It isn't automatically better or worse — it's a trade-off.

Q9Why is 0.75 commonly used?

It provides a practical balance between space efficiency and lookup performance.

Q10What is the difference between resizing and treeification?
ResizingTreeification
Increases the number of bucketsChanges one heavily collided bin into a tree

They solve different problems.

Q11Can a tree bin become a linked list again?

Yes. When the number of nodes falls sufficiently, the bin can be untreeified.

Q12Does HashMap use synchronization internally?

Normal HashMap is not designed as a thread-safe Map. Don't rely on its internal mechanics for synchronization — use ConcurrentHashMap when concurrent access is required.

#The Complete HashMap Mental Model

Memorize this. If you can explain it naturally, you understand HashMap internals at an interview-ready level.

HashMap → Node[] table
put(k, v)
hashCode()
hash spreading
(n - 1) & hash → bucket index
Bucket?
insert
collision → hash + equals()
hash + equals()
Same key?
replace
add node
Bin becomes large? → treeification
size > threshold? → resize
capacity × 2 → redistribute

#HashMap Interview Cheat Sheet

ConceptRemember
StructureHash table
Main arrayNode[] table
HashhashCode() + spreading
Spreadh ^ (h >>> 16) concept
Bucket index(n - 1) & hash
CapacityPower of two
Default initial capacity16
Default load factor0.75
Thresholdcapacity × load factor
CollisionDifferent keys → same bucket
Duplicate keyequals() true → replace value
Collision structureLinked nodes / tree
Tree typeRed-Black Tree
Tree threshold8
Minimum treeify capacity64
Untreeify thresholdCommonly 6
Average lookupO(1)
Heavy linked collisionO(n)
Tree bin lookup≈ O(log n)
ResizeUsually doubles capacity
Resize redistributionOld index or old index + old capacity
Null keyOne allowed
Null valueAllowed
OrderingNone guaranteed
Thread-safe❌ No
IteratorFail-fast, best-effort
Mutable keysDangerous
Comparable required for treeification?❌ No
Tree bins make HashMap sorted?❌ No

#Chapter Summary

When asked "How does HashMap work?", don't give a 30-second shallow answer. Use this sequence:

  1. HashMap maintains a bucket array.
  2. On put/get, it obtains the key's hashCode.
  3. The hash is spread to improve distribution.
  4. The bucket index is calculated using (n - 1) & hash.
  5. If the bucket is empty, insert.
  6. If occupied, compare hashes and keys using equals().
  7. Same key → replace value.
  8. Different key → collision structure.
  9. Heavy collision → treeification when conditions are met.
  10. When size exceeds the threshold → resize the table.
  11. Capacity normally doubles.
  12. Entries are redistributed efficiently using the additional hash bit.
  13. Average lookup is O(1).
  14. HashMap is not thread-safe.

That answer demonstrates both API-level understanding and implementation-level knowledge.