How does HashMap handle null keys/values, what is its iteration order, and why is it not thread-safe?
answer
- One null key (bucket 0); many null values allowed
- get()==null is ambiguous -> use containsKey()
- Iteration order unspecified + changes after resize
- Not thread-safe: CME, lost updates, pre-8 resize cycle
- Use ConcurrentHashMap (bans nulls) / LinkedHashMap / TreeMap
basics
~20 sHashMap allows one null key (stored in bucket 0) and any number of null values. Its iteration order is undefined and can change after a resize, so never rely on it. It is not thread-safe: concurrent modification can corrupt it or loop forever, so use ConcurrentHashMap for shared access.
solid answer
~50 sHashMap permits exactly one null key, mapped to bucket index 0 (hash of null is 0), and unlimited null values. Because a null value is allowed, get() returning null is ambiguous - it could mean 'absent' or 'present with null value' - so use containsKey() to distinguish. Iteration order is unspecified and not stable: it depends on the hash spread and bucket layout, and a resize re-buckets entries and can change the order. If you need order use LinkedHashMap (insertion/access order) or TreeMap (sorted). HashMap is not synchronized; concurrent structural modification by multiple threads can lose updates, throw ConcurrentModificationException via fail-fast iterators, or in pre-Java-8 versions even spin in an infinite loop during resize. For concurrency, prefer ConcurrentHashMap (which, unlike HashMap, forbids null keys and values). Modifying a HashMap during single-threaded iteration also triggers ConcurrentModificationException unless you use the iterator's own remove().
code
java · 12 linesMap<String, String> m = new HashMap<>();
m.put(null, "nullKeyOk"); // one null key allowed (bucket 0)
m.put("a", null); // null values allowed
String v = m.get("a"); // returns null...
boolean present = m.containsKey("a"); // true -> distinguishes 'present with null' from 'absent'
// Removing during iteration safely:
for (var it = m.entrySet().iterator(); it.hasNext(); ) {
var e = it.next();
if (e.getValue() == null) it.remove(); // NOT m.remove(...), which would throw ConcurrentModificationException
}go deeper
Knows one null key and many null values are allowed and that HashMap is not thread-safe.
Explains the get()/null ambiguity, that order is unspecified and changes on resize, and names ConcurrentHashMap/LinkedHashMap/TreeMap as alternatives.
Details fail-fast iterators/modCount, the pre-Java-8 resize cycle, and why ConcurrentHashMap bans nulls.
Advises on concurrency strategy (CHM vs synchronizedMap vs immutable maps), the ordering contract as an API guarantee, and migration pitfalls when code accidentally depends on iteration order.
## Null handling - **Null key**: HashMap allows **one** null key. Its `hash()` function returns `0` for null, so the null key always lives in **bucket 0**. A second put with a null key overwrites the first (keys are unique). - **Null values**: HashMap allows **any number** of null values - several keys can map to null. - **The get() ambiguity**: since values may be null, `map.get(k) == null` does not tell you whether `k` is absent or present-with-null-value. Use `map.containsKey(k)` to distinguish. (This is one reason `ConcurrentHashMap` bans nulls entirely - it removes the ambiguity in concurrent code.) ## Iteration order HashMap makes **no guarantee** about iteration order, and the order is **not stable over time**: - Entries are visited bucket by bucket, and within a bucket along its chain. Which bucket a key lands in depends on its spread hash and the current capacity. - A **resize** moves entries into a larger table (the low/high split), changing which bucket many entries occupy - so iteration order can differ before and after a resize, even with the same keys. Never rely on, log, or test against HashMap order. If you need a defined order: - **LinkedHashMap** maintains a doubly-linked list across entries for **insertion order** (or access order if configured). - **TreeMap** keeps keys **sorted** by natural order or a Comparator. ## Why HashMap is not thread-safe HashMap has **no internal synchronization**. With concurrent access where at least one thread structurally modifies it (put/remove/resize): - **Lost updates / corruption**: two threads resizing or inserting simultaneously can leave the table in an inconsistent state. - **Infinite loop (pre-Java 8)**: the old resize used head-insertion and could create a **cycle** in a bucket's linked list under a race, making a later get() spin forever and peg a CPU. Java 8's tail-insertion split removed that specific cycle, but HashMap remains unsafe for concurrent writes. - **Fail-fast iterators**: HashMap's iterators track a `modCount`; if the map is structurally modified during iteration (by another thread, or by the same thread not using `iterator.remove()`), the next `next()` throws **ConcurrentModificationException** (CME). This is a best-effort bug detector, not a guarantee. ## The safe alternatives - **ConcurrentHashMap**: built for concurrency - lock-striped/CAS-based, no full-map lock for reads, scales well. It **forbids null keys and null values** (so `get()==null` unambiguously means absent). Prefer it for shared mutable maps. - **Collections.synchronizedMap(new HashMap<>())**: wraps every method in a single lock - simple but coarse; iteration still needs manual external synchronization and is fail-fast. ## ConcurrentModificationException even single-threaded Even in one thread, mutating a HashMap while iterating its keySet/entrySet (other than via the iterator's own `remove()`) throws CME. To remove during iteration, use `iterator.remove()` or `map.entrySet().removeIf(...)`.
- How do you tell whether a key is absent or mapped to null?Use containsKey(key). get(key) returns null in both cases (absent, or present with a null value), so it cannot distinguish them.
- What ordered alternatives exist if you need predictable iteration?LinkedHashMap for insertion order (or access order for LRU-style use) and TreeMap for keys sorted by natural ordering or a Comparator.
saying these in an interview costs you the question
- Claiming HashMap preserves insertion order (that is LinkedHashMap)
- Saying HashMap allows no null keys (it allows exactly one)
- Thinking get() returning null always means the key is absent
- Calling HashMap thread-safe or 'safe for reads while another thread writes'
- Saying ConcurrentHashMap allows null keys/values (it forbids both)