Interview question
Coalesce concurrent Java loads for the same key
Implements single-flight request coalescing so one load runs per key while unrelated keys remain concurrent.
TL;DR
Implements single-flight request coalescing so one load runs per key while unrelated keys remain concurrent.
ConcurrentHashMap, CompletableFuture, race ownership, exception propagation, cleanup, and the difference between coalescing and caching.
Practice the problem like a real interview: restate, reason, implement, and test.
Implement SingleFlight<K,V>. If several callers request the same key concurrently, exactly one caller runs the loader and all callers observe its result or failure. Different keys may load in parallel. A completed or failed load must be removed so a later call can try again; this component is not a permanent cache.
Move to the linked follow-up, next path step, prerequisite, or deeper variant.
Answer a similar question in a different context.
load("A") + load("A") + load("A") -> loader for A runs once
load("A") + load("B") -> A and B may run in parallel
failed load("A"), then load("A") -> loader runs again
null keys and loader functions.putIfAbsent to decide whether this caller owns the load.finally, remove the mapping with remove(key, mine) so an older completion cannot remove a newer flight.import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.function.Supplier;
public final class SingleFlight<K, V> {
private final ConcurrentHashMap<K, CompletableFuture<V>> inFlight =
new ConcurrentHashMap<>();
private final Executor executor;
public SingleFlight(Executor executor) {
this.executor = Objects.requireNonNull(executor, "executor");
}
public CompletableFuture<V> load(K key, Supplier<V> loader) {
Objects.requireNonNull(key, "key");
Objects.requireNonNull(loader, "loader");
var mine = new CompletableFuture<V>();
var existing = inFlight.putIfAbsent(key, mine);
if (existing != null) {
return existing;
}
try {
executor.execute(() -> {
try {
mine.complete(loader.get());
} catch (Throwable failure) {
mine.completeExceptionally(failure);
} finally {
inFlight.remove(key, mine);
}
});
} catch (RuntimeException schedulingFailure) {
inFlight.remove(key, mine);
mine.completeExceptionally(schedulingFailure);
}
return mine;
}
}
Map coordination is expected O(1) per call. Memory is O(k + w), where k is the number of distinct in-flight keys and w is the number of waiting futures held by callers.
load returns; the returned future is still valid.computeIfAbsent with a long blocking loader can hide difficult recursion and contention behavior.remove(key) lets an old completion delete a newer in-flight value.Spot a weak answer, missing edge case, or clearer explanation? Send it in.
Say thanks with a standalone, one-time $5 contribution. No account required.