What Happens If You Modify a Key After Adding It to HashMap?
If you modify the fields of a key object after put(), HashMap won't be able to find that element. The object remains in the map but becomes "invisible" — it can't be retrieved,...
🟢 Junior Level
If you modify the fields of a key object after put(), HashMap won’t be able to find that element. The object remains in the map but becomes “invisible” — it can’t be retrieved, updated, or deleted.
Example:
MutableKey key = new MutableKey(1);
map.put(key, "Hello"); // Landed in bucket #1
key.setValue(2); // Modified the key — hash is now different
System.out.println(map.get(key)); // null!
// put(key, "Hello"): key.hashCode() = 1 → bucket #1
// key.setValue(2): key.hashCode() now = 2
// get(key): computes hash = 2 → searches in bucket #2 → null!
// Element is physically in bucket #1 — but get() searches in #2
Analogy: You changed your address but didn’t notify the bank. The bank sends you a letter to the old address — you won’t receive it.
🟡 Middle Level
Step-by-Step Failure Mechanics
- Addition:
map.put(user, "Active")—user.hashCode() = 100→ bucket #100 - Modification:
user.setName("New")—user.hashCode() = 200 - Search:
map.get(user)— computes hash 200 → bucket #200 →null
Paradox: You have a direct reference to the key object, but can’t retrieve the value!
Visibility via Iterator
When iterating over entrySet(), you can see this element:
for (Map.Entry<MutableKey, String> e : map.entrySet()) {
System.out.println(e.getKey() + " = " + e.getValue());
// Prints: MutableKey(id=2) = Hello
}
The iterator traverses all physical buckets, so it finds the “lost” element.
How to Fix?
The simplest way — create a new map:
Map<MutableKey, String> newMap = new HashMap<>(oldMap);
// On copying, the actual hash is computed for each key
Alternative: remove the old key via the entrySet() iterator, modify, insert back.
Common Mistakes
- Modifying entity ID — the most common case
- Modifying name/email — if they participate in hashCode
- JPA Entity as a key — Entity changes on persist/merge
🔴 Senior Level
Ghost Entry Pattern
“Ghost” entries are a memory leak that:
- Isn’t detected by standard profilers (object is reachable)
- Grows proportionally to the number of modifications
- Manifests only through OutOfMemoryError
// In production: service updates keys and puts new versions
Map<ConfigKey, Config> cache = new HashMap<>();
// On each update: old key is "lost", new one is added
// After 1000 updates: 999 ghost entries in memory
Comparison with IdentityHashMap
Map<MutableKey, Value> map = new IdentityHashMap<>();
// Uses System.identityHashCode() — object address
// Address doesn't change on field modification → works correctly
But semantics differ: new Key(1) and new Key(1) — different keys.
Internal Mechanics
HashMap on get():
- Computes
hash(key)— uses the current hashCode - Goes to the bucket by the current index
- Doesn’t find the element (it’s in the old bucket)
HashMap doesn’t store a “snapshot” of the key’s hash. Each get() computes the hash fresh.
Edge Cases
- Partial modification: Changed a field, but hashCode didn’t change (e.g., field not in hashCode) — works, but fragile
- Restoration: If fields are reverted to original state — hashCode matches, element is “found”
- Concurrent modification: One thread modifies the key, another searches — race condition. Result depends on thread execution order: lost updates or stale reads are possible.
Production Monitoring
For detection:
- Compare
map.size()with the number of unique keys via iterator - Use JFR to analyze bucket distribution
- Heap dump shows ghost entries (elements with “wrong” hash)
Defensive Strategy
// Pattern: Delete → Modify → Insert
Value v = map.remove(key);
key.modify();
map.put(key, v != null ? v : newValue);
Or use immutable keys:
record Key(String id) {} // Impossible to modify
🎯 Interview Cheat Sheet
Must know:
- Element stays in the old bucket, get() searches in the new → null
- Iterator entrySet() SEES the “lost” element — traverses all physical buckets
- Paradox: you have a direct reference to the key, but can’t retrieve the value
- HashMap doesn’t store a hash snapshot — each get() computes hashCode fresh
- Ghost Entry Pattern: memory leak, grows proportionally to modifications
- Solution: Delete → Modify → Insert pattern, or immutable keys
Common follow-up questions:
- Can you delete a lost element? — via entrySet() iterator yes, via remove() no
- If fields are reverted — will it be found? — yes, hashCode matches, element is “found”
- How does this differ from the mutable-key problem? — it’s a specific case: key was already in Map, then modified
- Why does the iterator see the element? — iterator physically traverses all buckets, not by hash
Red flags (DO NOT say):
- “The element is automatically deleted” — no, it stays in memory
- “HashMap rewrites the element to a new bucket” — no, it stays in the old place
- “This is safe if changed back” — fragile decision, race condition in multi-threading
Related topics:
- [[12. Can You Use a Mutable Object as a Key in HashMap]]
- [[14. What Are the Requirements for a HashMap Key]]
- [[19. What Happens During Rehashing]]