Build a Custom CompletableFuture: Async Primitive and Parallel Array Processing
GoogleSoftware Engineer · Technical ScreenLast updated
Implement a thread-safe asynchronous promise/future primitive with chaining and an aggregate fan-in barrier (`all_of`) for parallel processing.
Problem Statement
Modern asynchronous frameworks rely on future/promise abstractions to coordinate parallel tasks without blocking threads unnecessarily. In this problem, you will implement a thread-safe asynchronous primitive called SimpleFuture and use it to coordinate concurrent execution.
Implement the SimpleFuture class with the following methods:
complete(value: T) -> bool:- Sets the result of the future to
valueand transitions its state from pending to completed. - Invokes all registered callbacks with the resolved value.
- If the future has already been completed, subsequent calls to
completemust do nothing and returnFalse. ReturnsTrueon the first successful completion.
- Sets the result of the future to
get(timeout: float | None = None) -> T:- Blocks the calling thread until the future is completed and returns the value.
- If
timeoutis provided (in seconds) and expires before completion, raisesTimeoutError.
then(fn: Callable[[T], R]) -> SimpleFuture[R]:- Registers a transform function
fnto run when this future completes, returning a newSimpleFutureholding the result offn(value). - If this future is already completed when
thenis called,fnshould be executed immediately (or scheduled).
- Registers a transform function
all_of(futures: list[SimpleFuture[T]]) -> SimpleFuture[list[T]](static method):- Returns a new future that completes only when all provided input futures complete.
- Resolves with a list of results preserving the order of the input list.
- If the input list is empty, returns an immediately completed future with an empty list.
All state transitions, callback registrations, and completions must be safe for concurrent access by multiple threads.
Interface
from typing import Generic, TypeVar, Callable, Optional, List
import threading
T = TypeVar('T')
R = TypeVar('R')
class SimpleFuture(Generic[T]):
def __init__(self):
pass
def complete(self, value: T) -> bool:
pass
def get(self, timeout: Optional[float] = None) -> T:
pass
def then(self, fn: Callable[[T], R]) -> 'SimpleFuture[R]':
pass
@staticmethod
def all_of(futures: List['SimpleFuture[T]']) -> 'SimpleFuture[List[T]]':
pass
Examples
Example 1: Basic Completion and Chaining
f = SimpleFuture()
chained = f.then(lambda x: x * 2)
assert f.complete(10) is True
assert f.complete(20) is False # Already completed
assert chained.get() == 20
Example 2: Fan-in with all_of
f1 = SimpleFuture()
f2 = SimpleFuture()
aggregate = SimpleFuture.all_of([f1, f2])
def worker(fut, val):
fut.complete(val)
t1 = threading.Thread(target=worker, args=(f1, "A"))
t2 = threading.Thread(target=worker, args=(f2, "B"))
t1.start(); t2.start()
t1.join(); t2.join()
assert aggregate.get() == ["A", "B"]
Constraints
- Number of futures combined via
all_of:0 <= n <= 10^4. - Any number of threads may concurrently call
complete,get, orthenon the same future instance. - Callback functions
fnpassed tothenare guaranteed not to raise exceptions.
Edge Cases
thencalled aftercompletehas already been called.all_ofpassed an empty list.- Calling
getwithtimeout=0.0on an incomplete future must raiseTimeoutErrorimmediately.
Hints
Hint 1
Use a single `threading.Condition` or `threading.Lock` to guard the state transition (pending to completed) and the list of registered callbacks.
Hint 2
In `then()`, check if the future is already complete under the lock; if it is, run the callback outside the lock to avoid deadlocks. Otherwise, store a callback that completes the child future.
Hint 3
For `all_of`, maintain an atomic remaining-count integer and pre-allocate a list of size `len(futures)` to place results at their corresponding indices as each future completes.
Follow-up questions
- How would you extend this design to support exception propagation and a `.catch()` or `exceptionally()` handler?
- If a chain of `.then()` callbacks is very deep, synchronous execution can overflow the call stack or tie up the completing thread. How would you offload execution to a thread pool?
- How would you implement a `.cancel()` method that prevents unstarted chained callbacks from executing?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.