What does computeIfAbsent do, and why is it the idiomatic way to build multimaps (Map of List)?
answer
- computeIfAbsent: get-or-create-and-store, lazy
- multimap: computeIfAbsent(k, x -> new ArrayList<>()).add(v)
- created exactly once per key
- don't modify other keys inside the function
- null result => no entry created
basics
~20 scomputeIfAbsent(key, fn) returns the existing value for key, or if it is missing, runs fn to create a value, stores it, and returns it. It is perfect for grouping: it makes the empty list once, then you add to it.
solid answer
~40 s`computeIfAbsent(key, mappingFunction)` checks the map: if `key` already has a non-null value it returns that and does nothing else; if absent (or null), it calls `mappingFunction.apply(key)`, stores the result (unless the result is null), and returns it. The mapping function is **lazy** — invoked only on a miss — and receives the key. The canonical use is the 'group-by' / multimap pattern: `map.computeIfAbsent(k, key -> new ArrayList<>()).add(item);`. This collapses the get-null-check-create-put-then-add idiom into one line and guarantees the list is created exactly once per key. Caveats: do not structurally modify the same map *inside* the mapping function (undefined/ConcurrentModificationException), and on `ConcurrentHashMap` the function may run under a lock per bin, so keep it short and non-blocking. If the function returns null, no mapping is created.
go deeper
Recognizes computeIfAbsent as get-or-create and can write the multimap one-liner.
Explains lazy invocation, exactly-once creation, and why getOrDefault is wrong for grouping.
Discusses the no-self-modification rule and ConcurrentHashMap atomicity/locking implications.
Reasons about deadlock risks with nested computeIfAbsent on concurrent maps and chooses data structures to keep the mapping function cheap and reentrancy-safe.
### The pattern it replaces A very common need is: 'get the value for this key; if there isn't one, create a sensible initial value, store it, and use it.' Written by hand: ```java List<Item> list = map.get(key); if (list == null) { list = new ArrayList<>(); map.put(key, list); } list.add(item); ``` Four lines, easy to get wrong. `computeIfAbsent` does it in one. ### What computeIfAbsent does, precisely `map.computeIfAbsent(key, mappingFunction)`: 1. If `key` is present with a **non-null** value, return that value; the function is **not** called. 2. Otherwise call `mappingFunction.apply(key)` (it receives the key). 3. If the function returns a **non-null** result, store `key -> result` and return it. 4. If the function returns **null**, leave the map unchanged and return null. The function is a `Function<K, V>` — it is **lazy** (runs only on a miss), which is why it is preferred over `putIfAbsent` when the default is expensive to build. ### The multimap / group-by idiom A 'multimap' is a map where each key holds a *collection* of values. With `computeIfAbsent`: ```java Map<String, List<String>> byFirstLetter = new HashMap<>(); for (String name : names) { String letter = name.substring(0, 1); byFirstLetter.computeIfAbsent(letter, k -> new ArrayList<>()).add(name); } ``` On the first occurrence of a letter, the empty list is created and stored; on later occurrences the **same** list is returned. The `.add(name)` then runs on that list. The empty list is created **exactly once** per key — no duplicate allocations, no null checks. ### Important caveats - **No self-modification:** the mapping function must not add or remove *other* keys of the same map. Doing so can throw `ConcurrentModificationException` or corrupt the map; the behavior is undefined. - **Concurrency:** on `ConcurrentHashMap`, computeIfAbsent runs the function atomically and may hold a lock on that hash bin while it runs. Keep the function fast and non-blocking, and never call another `computeIfAbsent` on the same map inside it (risk of deadlock/livelock). - **Null result = no entry:** returning null from the function creates nothing. - **Existing null:** like the other default methods, a key currently mapped to null is treated as absent, so the function runs. ### Why it beats getOrDefault here `getOrDefault(k, new ArrayList<>())` would allocate a **new list every call** and never store it, so the added items would be lost. `computeIfAbsent` allocates lazily and persists the value.
- Why is computeIfAbsent(k, x -> new ArrayList<>()).add(v) correct but getOrDefault(k, new ArrayList<>()).add(v) buggy?computeIfAbsent stores the new list in the map and returns it, so subsequent adds hit the same list. getOrDefault returns a fresh, unstored list each time, so every added item is dropped.
- What happens if the mapping function returns null?No mapping is created and computeIfAbsent returns null; the key stays absent.
saying these in an interview costs you the question
- Using getOrDefault(k, new ArrayList<>()).add(v) (list is never stored)
- Calling computeIfAbsent recursively on the same ConcurrentHashMap
- Believing the function runs even when the key is present
- Doing heavy/blocking work in the mapping function on a ConcurrentHashMap