futures, promises, and exceptions
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Futures and promises are two sides of an asynchronous result. One side produces the value later, and the other side waits for or observes that result. Exceptions matter because asynchronous work can fail just as normal function calls can fail, but the failure has to cross a time boundary and often a thread boundary.
Promise as Producer, Future as Consumer
The cleanest mental model is:
- a promise is completed by the producer
- a future is observed by the consumer
The producer says, "I will provide a result later." The consumer says, "Tell me when that result or error is ready."
C++ makes this split explicit.
The worker owns the writable side. The caller owns the readable side.
Why Exceptions Need a Delivery Path
In synchronous code, a function can throw directly to its caller because both exist on the same call stack. In asynchronous code, the computation may finish later on another thread, so the exception cannot be thrown immediately to the original caller.
Instead, the failure must be stored until the consumer asks for the result.
The exception is rethrown when get is called. That keeps success and failure on the same asynchronous channel.
Futures Reduce Shared-State Coordination
Without a future or similar abstraction, developers often end up building ad hoc state sharing with:
- flags
- mutexes
- condition variables
- polling loops
A future simplifies that into one clear contract: one eventual outcome.
That makes futures a natural fit for:
- background computation
- thread-pool tasks
- RPC results
- long-running I/O completion
They are not a substitute for every concurrency pattern, but they are a strong fit when the result is single-shot.
Broken Promises and Missing Completion
A promise must be completed exactly once with either:
- a value
- an exception
If the promise is abandoned without either one, the future usually reports some form of broken-promise error. That is not just an API quirk. It signals that the producer violated the async contract.
This is why robust promise code always handles the failure path explicitly.
Relationship to Other Languages
The terminology varies, but the concept is widespread.
Examples:
- JavaScript
Promisecombines producer and consumer API behavior in one object. - C# often uses
Taskfor the readable side andTaskCompletionSourcefor manual completion. - Scala and some other languages expose
FutureandPromiseas separate objects, much like C++.
The naming is different, but the exception rule is stable: async failure should travel with the async result.
Common Pitfalls
- Forgetting to propagate exceptions into the promise, which leaves the consumer without the true failure cause.
- Thinking futures are general shared-state containers instead of single eventual results.
- Calling
getwithout knowing whether the future type is single-consumer or shareable. - Overusing manual promises where a higher-level async API already handles completion for you.
- Treating asynchronous exceptions as second-class errors instead of part of the normal result contract.
Summary
- A promise is the writable side of an eventual result, and a future is the readable side.
- Exceptions in asynchronous work must be captured and delivered through the future.
- Futures are useful when one producer eventually yields one success or one failure.
- Abandoning a promise without completion breaks the async contract.
- Good async design treats failure propagation as just as important as value propagation.

