skip to content

What is Map.merge and how does it express fold-style updates like frequency counting?

level: middleimportance: should knowfreq 62%

answer

  1. merge(k, value, fn): seed if absent, else fn(old, value)
  2. counts.merge(w, 1, Integer::sum) = counting idiom
  3. function takes two VALUES, not the key
  4. return null => remove entry
  5. atomic per key on ConcurrentHashMap

basics

~20 s

merge(key, value, fn) stores value if the key is absent; if the key already has a value, it calls fn(oldValue, value) and stores the result. It is the clean way to count: counts.merge(word, 1, Integer::sum).

solid answer

~50 s

`merge(key, value, remappingFunction)` combines an incoming value with whatever is already there. If the key is absent (or null), it simply stores the given `value`. If the key has a non-null value, it calls `remappingFunction.apply(oldValue, value)` and stores the result — or removes the entry if the function returns null. The classic use is frequency counting: `counts.merge(word, 1, Integer::sum)` adds 1 the first time and accumulates thereafter, with no null check. Note the function takes the **two values** (old and new), not the key — unlike compute. It also supports string concatenation, set unions, or any associative fold. On `ConcurrentHashMap`, merge is atomic per key, so it is the right primitive for concurrent accumulation. As with the compute family, returning null deletes the entry, and the remapping function should be free of side effects since it may be retried.

go deeper

for a junior

Can write counts.merge(word, 1, Integer::sum) and explain it counts occurrences.

for a middle

Explains the absent-vs-present branches, that the function takes two values, and the delete-by-null rule.

for a senior

Chooses merge vs compute appropriately and knows merge is atomic per key on ConcurrentHashMap.

for a principal

Reasons about associativity, function purity under retry, and lock-free accumulation design across concurrent maps.

### The need: combine new with existing Many updates are 'fold' operations: you have an existing accumulated value and a new contribution, and you want to combine them — sum counts, concatenate strings, union sets. Doing this by hand needs a null check for the first contribution: ```java Integer cur = counts.get(word); counts.put(word, cur == null ? 1 : cur + 1); ``` `merge` packages exactly this. ### What merge does, precisely `map.merge(key, value, remappingFunction)`: 1. If `key` is absent or mapped to null, store `key -> value` and return `value`. (The function is **not** called.) 2. If `key` has a non-null `oldValue`, compute `newValue = remappingFunction.apply(oldValue, value)`. 3. If `newValue` is non-null, store it and return it. 4. If `newValue` is null, **remove** the entry and return null. Note the remapping function is a `BiFunction<V,V,V>` over the **two values** (old, given) — it does **not** receive the key. The `value` argument must be non-null. ### The canonical counter ```java Map<String,Integer> counts = new HashMap<>(); for (String w : words) { counts.merge(w, 1, Integer::sum); } ``` First time a word is seen: absent, so `1` is stored. Subsequent times: `Integer::sum` combines the old count with `1`. No null check, one line. Compare to `compute(w, (k,v) -> v==null?1:v+1)` — merge is more concise for the 'seed-then-combine' shape because the seed/value is a parameter. ### Other folds - Concatenate: `map.merge(k, piece, String::concat)`. - Union sets: `map.merge(k, newSet, (a,b) -> { a.addAll(b); return a; })`. - Max/min: `map.merge(k, x, Integer::max)`. The function should be **associative** for predictable results when order varies. ### Delete by returning null Like compute, returning null from the remapping function **removes** the entry — useful to prune, e.g., a running balance that nets to zero. ### Concurrency On `ConcurrentHashMap`, `merge` does the whole read-combine-write **atomically** per key, so concurrent counters are race-free without external locks. Because the function can be retried under contention, keep it pure (the `Integer::sum`-style combiners are ideal). It may run under a per-bin lock, so keep it fast. ### merge vs compute Use `merge` when you have a concrete incoming value to fold in (counts, accumulations). Use `compute` when the new value depends on the key or on logic that doesn't fit the 'old + given' shape. Both share the return-null-removes rule and per-key atomicity on ConcurrentHashMap.

  • Does merge call the remapping function the first time a key is seen?
    No. On the first occurrence the key is absent, so merge simply stores the supplied value; the function runs only when an existing non-null value must be combined.
  • How does merge differ from compute in its function arguments?
    merge's BiFunction receives the two values (old, given) and not the key; compute's BiFunction receives the key and the current value (or null).

saying these in an interview costs you the question

  • Thinking the merge function receives the key (it gets old and new values)
  • Passing a null value argument to merge (not allowed)
  • Using merge when the new value depends on the key (use compute)
  • Assuming merge calls the function on the first insert

context