Hash collisions are where a simple map starts behaving like a real-world filing cabinet under pressure.
Question: Hash Collision in HashMap.
Answer: A hash collision happens when two different keys land in the same bucket of a HashMap. Java does not lose either entry; it keeps both in that bucket and uses equals() to find the exact one. So the main effect of a collision is usually slower lookup, not incorrect storage.
Interview-Ready Answer: I would say a hash collision in HashMap means two different keys map to the same bucket index. HashMap handles that by storing both entries in the same bucket and then checking equals() to pick the right key. In Java 8 and later, if one bucket gets too crowded, HashMap can switch that bucket from a linked list to a red-black tree, which keeps lookups from getting too slow.
Detailed Explanation: A HashMap uses an array of buckets. A bucket is just a slot that can hold one or more entries. A collision happens when two keys compute to the same bucket index, even if the keys are different. That is normal and expected; the map is designed to handle it.
hashCode() on the key, then mixes the bits with h ^ (h >>> 16) so weak low bits do less damage.(n - 1) & hash, where n is the table size. This works because HashMap keeps the capacity as a power of two.equals() to find the exact key. If the key matches, the value is replaced; if not, the new node is appended to that bucket.get(), HashMap repeats the same path: hash, index, bucket scan, then equals(). That is why a bad hashCode() hurts every read and write.| Shape | When used | Lookup | Trade-off |
|---|---|---|---|
| Linked list | Small collision chain | O(k) | Simple and small |
| Red-black tree | 8+ entries and table size 64+ | O(log k) | More memory, better worst case |
In the average case, get() and put() are O(1). With many collisions, a list bucket can degrade toward O(n) for that bucket, while a tree bucket keeps it closer to O(log n). Space is still O(n) overall because every entry must be stored somewhere.
hashCode() after insertion, the key may become hard to find because it now points to a different bucket.null key is allowed. HashMap stores it in bucket 0.Memory model: collisions are like many names assigned to the same mailbox; equals() is the clerk who opens the mailbox and hands you the right letter.
Real-World Story: In an e-commerce checkout service, the team used a HashMap to cache pricing data by PromotionKey. A refactor accidentally made hashCode() depend on only campaignId, while equals() still used both campaignId and customerId. During a flash sale, thousands of customers collided into the same few buckets, and checkout latency jumped from a few milliseconds to hundreds.
The symptoms were classic: CPU went up, profiler samples showed time inside HashMap.getNode(), and users saw spinner delays and occasional timeouts when applying coupons. The fix was to make the hash spread across the full key, add tests that compare equals() and hashCode() behavior, and avoid mutable fields in map keys.
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class Main {
static final class BadKey {
private final String id;
BadKey(String id) {
this.id = id;
}
@Override
public int hashCode() {
// Constant hash forces every key into the same bucket.
// This demonstrates that collisions are handled, not lost.
return 42;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof BadKey)) return false;
BadKey other = (BadKey) obj;
return Objects.equals(id, other.id);
}
@Override
public String toString() {
return "BadKey(" + id + ")";
}
}
static final class MutableKey {
private String id;
MutableKey(String id) {
this.id = id;
}
void setId(String id) {
this.id = id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof MutableKey)) return false;
MutableKey other = (MutableKey) obj;
return Objects.equals(id, other.id);
}
@Override
public String toString() {
return "MutableKey(" + id + ")";
}
}
public static void main(String[] args) {
Map<BadKey, String> map = new HashMap<>();
map.put(new BadKey("A"), "Apple");
map.put(new BadKey("B"), "Banana");
map.put(new BadKey("C"), "Cherry");
System.out.println("=== Collision demo ===");
for (String id : Arrays.asList("A", "B", "C")) {
// Even though every key has the same hash, equals() still finds the right entry.
System.out.println("Lookup " + id + " -> " + map.get(new BadKey(id)));
}
System.out.println("Map size after collisions: " + map.size());
System.out.println();
System.out.println("=== Mutable key edge case ===");
MutableKey key = new MutableKey("user-1");
Map<MutableKey, String> mutableMap = new HashMap<>();
mutableMap.put(key, "cached-profile");
System.out.println("Before mutation -> " + mutableMap.get(key));
// Changing a key after insertion is dangerous because the map now searches
// a different bucket than the one where the entry was originally stored.
key.setId("user-2");
System.out.println("After mutation with same object -> " + mutableMap.get(key));
System.out.println("Lookup with old value -> " + mutableMap.get(new MutableKey("user-1")));
System.out.println("Contains mutated key object? -> " + mutableMap.containsKey(key));
System.out.println("Map size still -> " + mutableMap.size());
}
}
Follow-up & Tricky Questions:
equals() to decide whether to replace an existing value or keep a separate entry.(n - 1) & hash instead of a slower modulo operation, and it helps resize redistribute entries efficiently.equals() decides whether it is the same logical key. Same hash alone just means they share a bucket.hashCode() first to find the bucket, then equals() only among candidates in that bucket. That is the whole reason hash lookups are fast on average.Common Mistakes:
equals().hashCode() or equals() after putting the key into the map.Memory Hook: Think of HashMap as a hotel: the hash is the room number, collisions mean two guests got the same room, and equals() is the front desk checking who really belongs there. Remember the 8-64 rule: at about 8 guests in one room and 64 rooms total, the hotel upgrades from a line to a tree.
Cheat Sheet:
hashCode() first, then equals().Practice Tasks:
hashCode() and confirm that three different keys still work in one HashMap.equals() and hashCode() correctly for a custom EmployeeId class, then write a small test that proves equal objects map to one entry.