Map the full grid of CompletableFuture two-input combinators by what they wait for and what callback they take.
answer
- Two axes: timing (Both vs Either) x callback (apply/accept/run)
- 3x2 grid → 6 methods
- apply=Function/value, accept=Consumer/void, run=Runnable/ignore
- Both uses BiFunction/BiConsumer (2 results); Either uses one-arg (winner only)
- allOf = many Both; anyOf = many Either; each has ...Async
basics
~20 sThere are six two-input methods in a 3x2 grid: wait for BOTH (thenCombine, thenAcceptBoth, runAfterBoth) or EITHER first (applyToEither, acceptEither, runAfterEither). Within each row the callback is a function (returns a value), a consumer (side effect), or a runnable (ignores results).
solid answer
~40 sCompletableFuture's two-input combinators form a tidy 3x2 grid. One axis is *when* the result fires: the 'Both' family (thenCombine, thenAcceptBoth, runAfterBoth) waits for both futures; the 'Either' family (applyToEither, acceptEither, runAfterEither) fires on the first to settle. The other axis is *what the callback returns*: the Combine/ApplyToEither pair takes a function and produces a value; the AcceptBoth/acceptEither pair takes a consumer of the result(s) and returns CompletableFuture<Void>; the runAfterBoth/runAfterEither pair takes a Runnable that ignores results, also Void. Combine uses a BiFunction (two results) while applyToEither uses a Function (only the winner). Each method also has ...Async overloads controlling the executing thread. For more than two inputs, allOf and anyOf are the many-input generalisations of the 'both' and 'either' columns respectively.
go deeper
Can name the 'both' and 'either' families and say one waits for both, one for the first.
Reproduces the full 3x2 grid, explains the timing and callback axes, and the BiFunction-vs-Function arity difference.
Adds the ...Async overloads (which thread runs the callback) and the allOf/anyOf generalisation to many inputs.
Reasons about thread-handoff and pool choice across the Async overloads and chooses combinators that minimise context switches and resource waste at scale.
## Why a grid? When you compose **two** `CompletableFuture`s, the API gives you six methods. They look like a confusing list, but they are really **two independent choices**, so they form a clean **3x2 grid**. Knowing the two axes lets you name any method on demand instead of memorising six. ## Axis 1 — When does the continuation fire? - **Both:** wait until **both** input futures complete. Names contain **'Both'** (or 'Combine'). This is a **join**. - **Either:** fire as soon as the **first** input settles; the slower is ignored. Names contain **'Either'**. This is a **race**. ## Axis 2 — What does the callback do with the result(s)? - **Function → value:** you transform the result(s) and the new future holds your returned value. Methods: `thenCombine` / `applyToEither`. - **Consumer → side effect, no value:** you consume the result(s) for an effect; the new future is `CompletableFuture<Void>`. Methods: `thenAcceptBoth` / `acceptEither`. - **Runnable → ignore results entirely:** you just want to run something once the timing condition holds; also `CompletableFuture<Void>`. Methods: `runAfterBoth` / `runAfterEither`. ## The grid | Callback \ Timing | **Both** (wait for both) | **Either** (first to settle) | |---|---|---| | **Function → value** | `thenCombine` (BiFunction) | `applyToEither` (Function) | | **Consumer → side effect** | `thenAcceptBoth` (BiConsumer) | `acceptEither` (Consumer) | | **Runnable → ignore** | `runAfterBoth` (Runnable) | `runAfterEither` (Runnable) | Note the arity difference in the top row: 'Both' sees **two** results, so it uses a **Bi**Function / **Bi**Consumer; 'Either' sees only the **winner**, so a plain Function / Consumer with **one** argument (and both inputs must share a type). ## The naming convention decoded The whole `CompletableFuture` API follows a consistent verb scheme: - `then...` / `...After...` = sequencing. - `apply` = transform with a Function (returns a value). - `accept` = consume with a Consumer (no value). - `run` = a Runnable (ignores inputs). - `Combine` / `Both` / `Either` = the two-input timing word. - `...Async` suffix = run the callback on a supplied executor (or the common pool) instead of the completing thread. So the name *is* the spec: `acceptEither` = 'consume (no value), on whichever finishes first'. ## Async overloads Every one of the six has `...Async` variants (with and without an `Executor`). The non-async form runs the callback on whichever thread completed the triggering input (could be a pool thread or even the caller if already done); the async form hands it to the common ForkJoinPool or your executor — relevant for blocking work or thread-affinity concerns. ## Scaling past two inputs The two columns generalise to **many** inputs: - **Both → many:** `allOf(cfs...)` → `CompletableFuture<Void>` (wait for all; read results from originals). - **Either → many:** `anyOf(cfs...)` → `CompletableFuture<Object>` (first to settle). ## Deriving your answer - Junior: name the two families. - Middle: reproduce the 3x2 grid and explain the two axes plus the BiFunction-vs-Function arity difference. - Senior: add the Async overloads and the allOf/anyOf generalisation.
- Why does thenCombine take a BiFunction but applyToEither only a Function?thenCombine waits for both futures, so its callback receives BOTH results — two arguments → BiFunction. applyToEither fires on the first to settle and only ever sees the winner's single result → a one-argument Function (and both inputs must share that type).
- What's the difference between the non-async and ...Async versions of these methods?The non-async form runs the callback on whatever thread completed the triggering future (or the caller if already complete). The ...Async form submits the callback to the common ForkJoinPool or a supplied Executor, decoupling it from the completing thread — important for blocking work or controlling thread affinity.
saying these in an interview costs you the question
- Mixing up the arity: using a BiFunction with applyToEither (it takes a one-arg Function)
- Thinking 'accept' methods return a value — they return CompletableFuture<Void>
- Forgetting that 'Either' needs both inputs to share a result type
- Confusing runAfterBoth (timing only) with thenAcceptBoth (consumes the results)