Why aren't my scala futures more efficient?
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.
Introduction
Scala Future makes asynchronous code easier to write, but it does not automatically make code faster. A program can use futures heavily and still run slowly because the real bottleneck is blocking I/O, the wrong execution context, too many tiny tasks, or contention on shared resources. The practical question is not "am I using futures" but "what work is actually running, where, and with how much coordination overhead."
A Future Is Not Free Parallelism
A Future represents a computation that may complete later. That does not mean the computation becomes cheaper or that more threads automatically help.
Simple example:
This code is asynchronous, but it still spends two seconds of blocked thread time in total. If enough futures block like that, the thread pool stops being productive.
The first mental model to keep is that Future improves coordination of asynchronous work. It does not eliminate the cost of the work itself.
Blocking Work Kills Throughput
The default global execution context is optimized for CPU-bound tasks, not arbitrary blocking I/O. If you call Thread.sleep, blocking database drivers, or slow network APIs inside futures, you can starve the pool.
Bad pattern:
Better pattern is to use a dedicated pool for blocking work:
If the underlying library offers non-blocking APIs, use those instead of wrapping blocking calls in a future and hoping the scheduler makes it efficient.
Too Many Tiny Futures Add Overhead
Developers sometimes split trivial work into thousands of futures. That can be slower than running a simple loop because scheduling, queueing, and synchronization costs dominate the actual computation.
For CPU-bound work, batch tasks at a meaningful grain size:
That is usually better than spawning one future per integer.
Composition Style Matters
Sequential dependency chains are often mistaken for parallel execution. Consider:
If fetchB depends on a, this is necessarily sequential. But if two futures are independent, start them before the for comprehension:
That is a common source of lost concurrency. The code may look asynchronous in both cases, but only the second version actually overlaps the independent work.
Shared Bottlenecks Still Dominate
Even perfectly composed futures cannot beat a shared bottleneck such as:
- one saturated database connection pool
- a rate-limited external API
- synchronized locks around mutable state
- a single disk or network bottleneck
If ten futures all wait on the same narrow resource, you do not get ten times the throughput. You only get more queued work and more scheduler overhead.
This is why performance analysis needs metrics beyond "number of futures created." Measure queue times, downstream latency, pool saturation, and blocking frequency.
Common Pitfalls
- Running blocking I/O on the default global execution context.
- Creating huge numbers of tiny futures for work that is too small to justify scheduling overhead.
- Writing sequential future chains and assuming they are parallel.
- Ignoring real downstream bottlenecks such as connection pools or synchronized state.
- Using futures as a performance strategy without measuring where time is actually spent.
Summary
- Futures improve coordination, not the intrinsic cost of the work being done.
- Blocking tasks should not run on the same pool as CPU-bound future work.
- Task granularity matters because many tiny futures can be slower than a plain loop.
- Independent futures must be started independently if you want actual overlap.
- Measure downstream bottlenecks before concluding that futures themselves are inefficient.
Related reading
- Why best case for insertion sort is On not On2?
- Why big-Oh is not always a worst case analysis of an algorithm?
- Why binary_crossentropy and categorical_crossentropy give different performances for the same problem?
- Why both training and testing loss decrease as I add more training data incrementally?
- Why async/await doesn't work in my case?
- Why Async.StartChild does not take CancellationToken?
- Why can GPU do matrix multiplication faster than CPU?
- Why can the KMP failure function be computed in On time?

DSA Fundamentals
Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.