What Happens If You Modify a Key After Adding It to HashMap
If you modify the state of a key's fields after inserting it into a HashMap:
🟢 Junior Level
Short Interview Answer (30 seconds)
If you modify the state of a key’s fields after inserting it into a HashMap:
- The Entry Becomes Inaccessible via Direct Key Lookup: Calling
map.get(key)returnsnull, andmap.containsKey(key)returnsfalse. This occurs becauseget()computes a new hash code from the mutated state and navigates to a new bucket, while the physical node remains anchored in the original bucket. - The Iterator Paradox: The map size does not change (
map.size()remains constant). When iterating viafor (var e : map.entrySet()), the entry is still found, because sequential iteration visits all buckets linearly, bypassing hash calculations. - Deletion Fails: Calling
map.remove(key)also hashes to the new bucket and returnsnull. The unlinked node becomes a “ghost entry”, leading to a persistent memory leak in the heap.
Demonstration in Java 21
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class MutatedKeyDemo {
public static class UserKey {
private String login; // Mutable field
public UserKey(String login) { this.login = login; }
public void setLogin(String login) { this.login = login; }
@Override
public boolean equals(Object o) {
return o instanceof UserKey k && Objects.equals(login, k.login);
}
@Override
public int hashCode() {
return Objects.hash(login);
}
}
public static void main(String[] args) {
Map<UserKey, String> sessions = new HashMap<>();
UserKey key = new UserKey("alice");
sessions.put(key, "ActiveSession_Alice"); // Stored in bucket derived from hash("alice")
// Pre-mutation lookup:
System.out.println("Lookup before mutation: " + sessions.get(key)); // ActiveSession_Alice
// Mutate key state:
key.setLogin("bob"); // Hash code changes dynamically in the heap!
// ❌ PARADOX 1: Direct lookup returns null
System.out.println("Lookup after mutation: " + sessions.get(key)); // null!
// ❌ PARADOX 2: Iterator still finds the "lost" entry
for (Map.Entry<UserKey, String> entry : sessions.entrySet()) {
System.out.println("Found by iterator: " + entry.getKey().login + " => " + entry.getValue());
// Prints: "Found by iterator: bob => ActiveSession_Alice"
}
}
}
🟡 Middle Level
The Key Restoration Effect
A fascinating property of hash tables: if the mutated fields are restored to their exact original values, the element becomes retrievable again:
key.setLogin("bob");
System.out.println(sessions.get(key)); // null (hashes to the wrong bucket)
// Restore the original state:
key.setLogin("alice");
System.out.println(sessions.get(key)); // Successfully returns "ActiveSession_Alice"!
Because the node never left its original bucket slot in the table array, recalculating key.hashCode() with the original field values redirects the map back to the original bucket, matching the node immediately.
Behavior During Table Resizing (resize())
What happens to a mutated key when HashMap triggers a table resize?
Each Node<K,V> holds an immutable field final int hash, cached when put() was originally executed:
- During resizing,
HashMapnever callskey.hashCode()again! - It evaluates the cached field
e.hashto calculate the new index:if ((e.hash & oldCap) == 0) // Remains at oldIndex else // Moves to (oldIndex + oldCap) - Consequently, the node is re-indexed according to its original insertion hash, while
map.get(key)continues to query using the mutated hash. The desynchronization is permanently cemented into the table structure.
🔴 Senior Level
Node.hash Desynchronization: Accidental Bucket Collision
What happens if mutating a key results in a new hashCode() that accidentally hashes to the exact same bucket index? Will the item be found?
Answer: NO, it will still NOT be found!
Examine the exact matching condition inside HotSpot’s HashMap.getNode():
// OpenJDK HashMap.getNode():
if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k))))
- The node retains its cached field
first.hash(the pre-mutation hash, e.g.,100); - Calling
get(key)calculates the current hashhash = key.hashCode()(e.g.,116); - Even if both hashes map to bucket 4 with a 16-element capacity (
100 & 15 == 4and116 & 15 == 4), the very first primitive check:first.hash == hash$\implies$100 == 116returnsfalse! HashMapimmediately rejects the node and continues traversing the collision chain, ultimately returningnull. The mismatched cached hash prevents access even within the identical bucket.
Reference Duplication Anomaly: Two Nodes Pointing to One Object
If map.put() is called again with the mutated key instance:
UserKey key = new UserKey("alice");
map.put(key, "Version-1"); // Stored in table[3], Node.hash = hash("alice")
key.setLogin("bob");
map.put(key, "Version-2"); // Stored in table[11], Node.hash = hash("bob")
Heap Consequences:
- The map now contains two distinct
Node<K,V>objects in different buckets; - Both nodes hold references to the exact same
keyinstance in heap memory; map.size()equals 2;- Calling
map.get(key)returns"Version-2"; - The original
"Version-1"node is permanently trapped as an unmanageable “ghost entry”, unreachable viaget()becausekeyno longer yields the hash for"alice".
🎯 Interview Cheat Sheet
1. 30-Second Elevator Pitch
If a key is mutated after insertion into a HashMap, direct lookups via get() and deletions via remove() return null because the map searches the bucket corresponding to the updated hash code. However, size() does not decrease, and entrySet() iterator traversals still find the entry because iteration linearly scans all physical buckets. Even if the mutated hash happens to map to the same bucket index, lookup fails because the cached Node.hash does not match the query hash. Restoring the key’s fields to their original state restores full lookup capability.
2. 4 Tricky Interview Questions with Deep Answers
Question 1: What happens if you call map.put(key, newValue) with the same key instance after mutating its fields?
Answer:
HashMap calculates the new hash code and inserts a brand new Node<K,V> into the new bucket. The map now holds two distinct node entries referencing the exact same key instance in the heap, and size() increments by 1. The original entry becomes an un-removable ghost entry that cannot be updated or evicted through standard key APIs.
Question 2: Why will map.get() fail to find the mutated key even if the new hash code points to the exact same bucket?
Answer:
HashMap.getNode() begins with an equality check on the 32-bit hash value: if (first.hash == hash). HotSpot caches the original hash code inside the node upon insertion (first.hash). When get() evaluates the current mutated hash code, first.hash == hash evaluates to false in a single CPU cycle, bypassing reference and equals() checks entirely.
Question 3: What happens to a node with a mutated key during a table resize?
Answer:
No re-hashing of keys occurs during resize(). HashMap reads the immutable cached e.hash field already present on each node and uses bitwise partitioning ((e.hash & oldCap) == 0) to position the node in the expanded table. The node remains positioned according to its original pre-mutation hash, cementing the disconnect with the mutated key.
Question 4: How can an orphaned, mutated key entry be safely purged from the map without calling map.clear()?
Answer:
By performing a manual traversal using map.entrySet().iterator() and comparing key references:
Iterator<Map.Entry<UserKey, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
if (it.next().getKey() == mutatedKey) {
it.remove(); // Unlinks the node from its bucket chain
}
}
3. Common Pitfalls & Red Flags
-
❌ “When a key mutates, HashMap automatically shifts the node to its new bucket.”
Correction:HashMaphas no listeners or mechanisms to track external mutations. The node remains stuck in its original bucket. -
❌ “The mutated entry is collected by the Garbage Collector because the key is broken.”
Correction: The internaltable[]array maintains a strong reference to the node, and the node retains strong references to both key and value, causing an active memory leak. -
❌ “The iterator will also fail to find the mutated element.”
Correction:entrySet()iterates sequentially across array indices (table[0]...table[n-1]), exposing all nodes regardless of hash validity.
4. Related Topics
- Can You Use a Mutable Object as a Key in HashMap — Mutable key hazards
- What Are the Requirements for a HashMap Key — Immutability and hash contract rules
- What Happens During Rehashing — Bitwise split and cached hash mechanics
- What is the equals() and hashCode() Contract — Core invariants
- Why String Is Often Used as a Key in HashMap — Immutable key architecture