skip to content

How do compute and computeIfPresent differ from computeIfAbsent, and when would you use each?

level: middleimportance: should knowfreq 58%

answer

  1. IfAbsent: miss only, Function(key)
  2. IfPresent: hit only, BiFunction(key,value)
  3. compute: always, BiFunction(key, value-or-null)
  4. return null => remove the entry
  5. ConcurrentHashMap: atomic read-modify-write

basics

~20 s

computeIfAbsent runs only when the key is missing. computeIfPresent runs only when the key already has a value. compute always runs and receives the current value (or null). In all three, returning null removes (or skips creating) the entry.

solid answer

~40 s

The three 'compute' methods share a remapping shape but differ on when the function fires and what it sees. `computeIfAbsent(k, fn)` takes a `Function<K,V>` and runs only on a miss. `computeIfPresent(k, fn)` takes a `BiFunction<K,V,V>` (key + current value) and runs only when the key has a non-null value; returning null removes the entry. `compute(k, fn)` also takes a `BiFunction` but **always** runs, receiving the existing value or null for an absent key — letting you handle both insert and update in one function; returning null removes/declines the entry. Use computeIfAbsent for lazy initialization (multimaps), computeIfPresent for 'update only what exists' (e.g., decrement a stock count and drop it at zero), and compute for unconditional upsert logic. On ConcurrentHashMap all three are atomic per key, so the read-modify-write is race-free.

go deeper

for a junior

Can state that the three differ by when the function runs (absent / present / always).

for a middle

Knows the function signatures, that compute always runs, and the return-null-removes rule.

for a senior

Applies them correctly for upsert/decrement-and-remove and explains atomicity on ConcurrentHashMap.

for a principal

Designs lock-free accumulation patterns, reasons about function retries/side-effect freedom, and weighs delete-by-null pitfalls in shared maps.

### The shared idea: remapping All three methods compute a **new value** for a key from a function and atomically install it. They differ in (a) **when** the function runs and (b) **what arguments** it gets. In every case, **if the function returns null, the mapping is removed** (or, for an absent key, not created) — this is the unified 'delete by returning null' rule. ### computeIfAbsent(key, Function<K,V>) Runs the function **only if the key is absent** (or mapped to null). The function receives just the **key**. Used for lazy initialization — see the multimap pattern. If the key is present, the function does not run and the existing value is returned. ### computeIfPresent(key, BiFunction<K,V,V>) Runs the function **only if the key has a non-null value**. The function receives **(key, currentValue)** and returns the new value. If it returns null, the entry is **removed**. If the key is absent, nothing happens and it returns null. Typical use: modify something that must already exist, e.g. ```java stock.computeIfPresent(sku, (k, qty) -> qty > 1 ? qty - 1 : null); // remove at zero ``` ### compute(key, BiFunction<K,V,V>) **Always** runs. The function receives **(key, currentValueOrNull)** — null if the key is absent — so it must handle both the insert and update cases. Returning a value installs it; returning null removes the key (or leaves it absent). This is the general-purpose 'upsert'. Example, append to a running total whether or not it existed: ```java map.compute(k, (key, v) -> (v == null ? 0 : v) + delta); ``` ### Comparison table | Method | Function type | Runs when | Sees current value? | |---|---|---|---| | computeIfAbsent | Function<K,V> | key absent | no (key only) | | computeIfPresent | BiFunction<K,V,V> | key present | yes | | compute | BiFunction<K,V,V> | always | yes (or null) | ### Atomicity on ConcurrentHashMap On `ConcurrentHashMap`, each of these performs the entire **read-modify-write atomically** for that key — no other thread can interleave between reading the old value and writing the new one. That makes them the correct tool for concurrent counters and accumulators (versus `get` then `put`, which has a race). Keep the function short and side-effect-free, since it may run under a per-bin lock. ### The delete-by-null rule and side effects Because returning null deletes, be deliberate: a function that can return null on compute/computeIfPresent will *remove* entries — sometimes desired (clean up at zero), sometimes a surprise. Also, on ConcurrentHashMap the function may be retried, so it must be free of observable side effects.

  • What happens if a compute function returns null?
    The mapping for that key is removed (or, if the key was absent, no mapping is created). This is the shared delete-by-null rule across compute/computeIfPresent.
  • Why are compute methods preferred over get-then-put on a ConcurrentHashMap?
    They perform the read-modify-write atomically for the key, eliminating the race where two threads read the same old value and one overwrites the other's update.

saying these in an interview costs you the question

  • Thinking compute skips absent keys (it always runs)
  • Forgetting that returning null removes the mapping
  • Expecting computeIfPresent to insert a new key
  • Using get-then-put for concurrent counters instead of compute

context