What happens if you modify a mutable key in HashMap?
If you modify an object used as a key in HashMap, it gets lost — you won't be able to find it.
Junior Level
If you modify an object used as a key in HashMap, it gets lost — you won’t be able to find it.
List<String> key = new ArrayList<>(List.of("A"));
Map<List<String>, String> map = new HashMap<>();
map.put(key, "value");
key.add("B"); // Modified the key!
System.out.println(map.get(key)); // null — not found!
Why
- On
put, HashMap computed the hash of["A"]→ placed in bucket #5 - After key modification, hash of
["A", "B"]→ different bucket getsearches in the wrong bucket →null
The object is still in the map, but you can’t retrieve it.
Middle Level
Failure mechanics
- put(key, value) — key hash → bucket #5
- Mutation — changed a key field → new hash
- get(key) — new hash → bucket #10 → empty →
null
The object is physically still in bucket #5, but became unreachable.
Consequences
Logical unreachability: the object cannot be retrieved via get() or removed via remove() using the modified key. It occupies space until the entire map is cleared or entrySet() is iterated.
Duplicates in HashSet:
Set<List<String>> set = new HashSet<>();
set.add(new ArrayList<>(List.of("A")));
// Modified an element inside the list
set.add(new ArrayList<>(List.of("A"))); // Added again!
How to find the lost object
Through full iteration — O(n) instead of O(1):
for (var entry : map.entrySet()) {
if (entry.getKey().contains("A")) {
// found it
}
}
Senior Level
How to avoid
- Immutable keys —
String,UUID,Record— always - Remove → modify → add:
V value = map.remove(key); key.mutate(); map.put(key, value); - IdentityHashMap — if mutation is inevitable and you don’t control the object (comparison by
==)Map<List<String>, String> map = new IdentityHashMap<>(); // Comparison by ==, not equals — mutation doesn't break lookup as long as it's the same object.
The deep problem
Modifying a mutable key breaks the HashMap invariant: the key’s hashCode must be stable. This is not a HashMap bug, but a violation of the hashCode() contract — if equals hasn’t changed, then hashCode should be the same, but mutation changes both.
Summary for Senior
- Modifying a mutable key “breaks” the map — object is unreachable via
get() - This is a classic memory leak and source of hard-to-find bugs
- Always design keys as immutable entities
- If mutation is necessary:
remove→mutate→put IdentityHashMap— a last resort when key control is impossible
Interview Cheat Sheet
Must know:
- Mutable key modification → new hashCode → get searches in a different bucket → null
- Object is physically in the map, but became unreachable — logical memory leak
- Duplicates in HashSet: modified object is added again as “new”
- Lost object can only be found via entrySet iteration — O(n) instead of O(1)
- How to avoid: immutable keys always, or remove → mutate → put
- IdentityHashMap — compares by
==, mutation doesn’t break lookup (as long as it’s the same object)
Frequent follow-up questions:
- Is the object removed from the map? — No, it stays in the old bucket, but is unreachable via get/remove
- How to find a lost object? — Iterate entrySet — O(n)
- Does IdentityHashMap solve the problem? — Yes, compares by reference, but it’s a specific use case
- Is this a HashMap bug? — No, it’s a contract violation: hashCode must be stable
Red flags (do NOT say):
- “The object is removed from the map” — no, it stays but is unreachable
- “HashMap fixes the hashCode itself” — no, it’s the developer’s contract
- “Mutable key is fine” — classic source of memory leaks and bugs
- “IdentityHashMap is a replacement for HashMap” — it’s a completely different structure, compares by reference
Related topics:
- [[27. Can you use immutable objects as keys in HashMap]]
- [[22. What are the advantages of immutable objects for caching]]
- [[20. What is Record and how does it help create immutable classes]]
- [[14. What is the difference between shallow copy and deep copy]]