skip to content

How does HashMap handle null keys/values, what is its iteration order, and why is it not thread-safe?

level: middleimportance: must knowfreq 70%

answer

  1. One null key (bucket 0); many null values allowed
  2. get()==null is ambiguous -> use containsKey()
  3. Iteration order unspecified + changes after resize
  4. Not thread-safe: CME, lost updates, pre-8 resize cycle
  5. Use ConcurrentHashMap (bans nulls) / LinkedHashMap / TreeMap

basics

~20 s

HashMap 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 s

HashMap 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 lines
java
Map<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

for a junior

Knows one null key and many null values are allowed and that HashMap is not thread-safe.

for a middle

Explains the get()/null ambiguity, that order is unspecified and changes on resize, and names ConcurrentHashMap/LinkedHashMap/TreeMap as alternatives.

for a senior

Details fail-fast iterators/modCount, the pre-Java-8 resize cycle, and why ConcurrentHashMap bans nulls.

for a principal

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)

context