Hook: Interviewers ask this because put() looks tiny, but in HashMap it hides hashing, collision handling, and resize logic.
Question: Explain map.put() internal flow.
Answer: The exact flow depends on the Map implementation, but the classic interview answer is HashMap.put(). It computes a hash for the key, finds a bucket, checks whether the key already exists with equals(), and either overwrites the old value or inserts a new node. If too many keys land in one bucket, Java 8+ can turn that bucket into a balanced tree for faster lookup.
Interview-Ready Answer: I would say: map.put() in HashMap first hashes the key, then uses that hash to pick a bucket, and then either updates an existing key or adds a new node if the key is not already present. Collisions are handled inside that bucket, and in Java 8+ a long collision chain can be treeified, while a growing map may resize when it crosses its load threshold. So the average insert is O(1), but bad hashing or lots of collisions can make it slower.
In Java, Map is an interface, so the real flow depends on the implementation. In interviews, people usually mean HashMap.put(), because that is where hashing and collision handling matter most. A bucket is just one slot in the internal array; a collision means two different keys land in the same bucket.
HashMap.put() works under the hoodhashCode() and spreads the bits so keys distribute better. This is why a good hashCode() is critical.HashMap allocates it on first insert. The default initial capacity is 16 and the default load factor is 0.75, so the first resize threshold is 12.(n - 1) & hash. This fast bit operation works because the capacity is kept as a power of two.equals() to decide whether the key already exists. Same key means replace the value and return the old value.threshold = capacity * loadFactor, the table grows, usually by doubling. For the default map, that means resizing after the 13th entry.put() returns the previous value for that key, or null if there was no previous mapping.| Map | Insert path | Order | Typical cost |
|---|---|---|---|
| HashMap | Hash + equals | None | Avg O(1) |
| LinkedHashMap | Hash + equals | Insertion/access order | Avg O(1) |
| TreeMap | Comparator/compareTo | Sorted by key | O(log n) |
HashMap allows one null key and many null values; the null key is treated specially and lands in bucket 0.equals() or hashCode() change after insertion, future lookups can fail.modCount, which helps fail-fast iterators detect unexpected changes; replacing an existing value usually does not.HashMap is not thread-safe; concurrent put() without coordination can lose updates or corrupt behavior.Memory-wise, the main point is simple: put() is not just store-and-forget. It is hash, locate, compare, possibly grow, then return the old value.
Imagine a checkout service that keeps a HashMap of SKU to pricing rule. Every cart update does a put() to refresh promo data before calculating totals. One day, a developer uses a mutable object as the key and changes one of its fields after insertion. The map still contains the entry, but lookups start missing because the key now points to a different hash bucket. Users see wrong totals, logs show sudden cache misses, and the checkout team chases a ghost bug until they realize the key contract was broken. The lesson: put() is fast only when keys are stable and well-designed.
import java.util.HashMap;
import java.util.Map;
public class PutInternalFlowDemo {
// Force collisions: different keys share the same hash bucket.
static final class Key {
private final int id;
Key(int id) {
this.id = id;
}
@Override
public int hashCode() {
return 1; // same bucket for every Key instance
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof Key)) return false;
return id == ((Key) other).id;
}
@Override
public String toString() {
return "Key(" + id + ")";
}
}
// Mutable keys are dangerous in HashMap because the hash can change after insertion.
static final class MutableKey {
private int id;
MutableKey(int id) {
this.id = id;
}
void setId(int id) {
this.id = id;
}
@Override
public int hashCode() {
return id;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof MutableKey)) return false;
return id == ((MutableKey) other).id;
}
@Override
public String toString() {
return "MutableKey(" + id + ")";
}
}
public static void main(String[] args) {
Map<Key, String> map = new HashMap<>();
Key a1 = new Key(1);
Key a2 = new Key(2);
Key a1Copy = new Key(1);
// First insert into an empty map: no old value exists.
System.out.println("put(a1, Alice) returns: " + map.put(a1, "Alice"));
// Same bucket, different key: HashMap resolves the collision.
System.out.println("put(a2, Bob) returns: " + map.put(a2, "Bob"));
// Equal key: value is replaced, old value is returned.
System.out.println("put(a1Copy, Alice-updated) returns: " + map.put(a1Copy, "Alice-updated"));
// HashMap allows one null key. It always goes to bucket 0 internally.
System.out.println("put(null, NULL-VALUE) returns: " + map.put(null, "NULL-VALUE"));
System.out.println("Map after inserts: " + map);
System.out.println("size = " + map.size());
System.out.println("get(a1) = " + map.get(a1));
System.out.println("get(a1Copy) = " + map.get(a1Copy));
System.out.println("get(null) = " + map.get(null));
Map<MutableKey, String> mutableMap = new HashMap<>();
MutableKey mk = new MutableKey(10);
mutableMap.put(mk, "ten");
// Changing a key after insertion breaks lookups because the hash no longer points to the same bucket.
mk.setId(11);
System.out.println("After mutating the key, get(mk) = " + mutableMap.get(mk));
System.out.println("Lookup with new key 10 = " + mutableMap.get(new MutableKey(10)));
System.out.println("Mutable map contents: " + mutableMap);
}
}Follow-up & Tricky Questions:
put() return? It returns the previous value for that key, or null if the key was not mapped before. Watch out: null can also mean the key existed with a null value.HashMap checks equals() inside the bucket. It uses a linked list first, and in Java 8+ a long bucket can become a tree.capacity * loadFactor. With defaults, that means after the 12th entry is already there and the 13th goes in.(n - 1) & hash. That is faster than using modulo each time.equals() if we already have hashCode()? Hashes only narrow the search to a bucket. equals() confirms whether two keys are logically the same key.HashMap thread-safe? No. If multiple threads call put() concurrently, use ConcurrentHashMap or external synchronization.put() on the same key count as a structural change? Usually no. Replacing a value updates the mapping without changing the shape of the table, so it typically does not bump modCount the way a new entry does.equals() still decides whether they are actually the same mapping.Common Mistakes:
put() only inserts. Correction: it can also overwrite an existing value and return the old one.== mentally for key matching. Correction: HashMap uses hashCode() first and equals() to confirm key equality.put() and get().Memory Hook: Think of HashMap.put() like a mailroom: the hash finds the shelf, equals() finds the exact box, collisions stack boxes on the same shelf, and resize adds more shelves.
Cheat Sheet:
Map is an interface; HashMap.put() is the usual interview target.16, load factor is 0.75, default threshold is 12.(n - 1) & hash.Practice Tasks:
hashCode() and observe that the map still works, but more slowly.