ConcurrentHashMap Interview Questions
KOTLIN › Concurrency
In your own words, walk me through how ConcurrentHashMap achieves high write throughput without a single global lock. What actually gets locked, and why doesn't that create the same bottleneck as synchronizing on the whole map?
What a strong answer covers: Writes CAS-install the first node into an empty bin, and once a bin already has a node, only that bin's head node is synchronized on, not the whole table; reads never lock because node value/next fields are volatile, so a writer's lock release happens-before a reader's subsequent read. This gives fine-grained, per-bin locking instead of one lock guarding the entire table, so unrelated keys hashing to different bins never contend with each other.
You're building a shared cache using ConcurrentHashMap and computeIfAbsent to load-once-per-key. What's the trade-off here versus something like a dedicated cache library, and where does computeIfAbsent's locking start to hurt you?
What a strong answer covers: computeIfAbsent holds the target bin's lock for the duration of the lambda, so if the load is slow (a network call, disk read), it blocks every other put or compute that happens to hash-collide into that same bin, not just operations on that one key. A dedicated cache (or a per-key coroutine mutex with its own in-flight tracking) decouples load-in-flight coordination from the map's internal locking and adds features ConcurrentHashMap doesn't have, like TTL and size-based eviction; CHM plus computeIfAbsent is only the right call when the load itself is cheap and fast.
A junior engineer says 'ConcurrentHashMap is thread-safe so I don't need to worry about synchronization in my code.' What's wrong with that statement, and can you give a concrete example of a race that still slips through despite using ConcurrentHashMap throughout?
What a strong answer covers: ConcurrentHashMap only guarantees that individual operations are atomic; it says nothing about compound sequences spanning multiple calls, or about invariants spanning more than one key. A classic example is incrementing a counter via a plain read-then-put pair (val v = map[key]; map[key] = v + 1), which races even though each call is individually atomic, or trying to keep two related maps in sync since CHM can't atomically update both at once. The fix is the atomic compound operations (merge, compute) for single-key updates, and explicit locking or a different structure entirely for cross-key invariants.