C++
std::async
programming
concurrency
Bjarne Stroustrup

What Limitation of stdasync is Stroustrup Referring To?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

When Stroustrup points out limitations of std::async, he is usually referring to the fact that it gives you too little control over how work is scheduled and executed. The biggest practical issue is that the default launch policy allows the implementation to choose between real asynchronous execution and deferred execution. That makes behavior less predictable than many developers expect, and it also exposes the broader design limitation that std::async is not a full executor or thread-pool abstraction.

The Default Policy Is Ambiguous

If you call std::async without an explicit launch policy, the implementation may choose either:

  • 'std::launch::async,'
  • 'std::launch::deferred,'
  • or a combination policy.
cpp
1#include <future>
2#include <iostream>
3
4int compute() {
5    return 42;
6}
7
8int main() {
9    auto f = std::async(compute);
10    std::cout << f.get() << '\n';
11}

This looks like it “obviously” launches a new asynchronous task, but the standard allows the implementation to defer execution until get() or wait() is called.

That ambiguity is the first major limitation.

Why That Matters

If execution may be deferred, then code that looks concurrent may actually run synchronously at the point of get(). That affects:

  • performance expectations,
  • latency behavior,
  • scalability reasoning,
  • portability across implementations.

A developer may think they are scheduling parallel work, while the implementation is really just postponing the function call.

You Can Force A Policy, But That Only Solves Part Of The Problem

You can remove that particular ambiguity by writing:

cpp
auto f = std::async(std::launch::async, compute);

or:

cpp
auto f = std::async(std::launch::deferred, compute);

That helps, but it does not solve the deeper limitation: std::async still does not let you choose an executor, thread pool, queueing policy, or scheduling strategy.

The Bigger Limitation: No Execution Model Control

Modern concurrency design often wants more than “run this eventually.” It wants explicit control over:

  • which pool runs the work,
  • how many worker threads exist,
  • what happens under load,
  • how tasks are composed,
  • how cancellation or prioritization is handled.

std::async does not provide that. It is a convenience API, not a complete scheduling framework.

This is one reason many serious systems end up using:

  • explicit thread pools,
  • task schedulers,
  • executors,
  • library-specific concurrency frameworks.

Future Semantics Add More Surprise

Another source of discomfort is the interaction between futures and waiting behavior. In some situations, developers discover that simply holding or destroying a future can affect when synchronization happens, which is more subtle than “fire-and-forget task launch.”

That makes std::async convenient for small examples but less satisfying as a foundation for large, predictable concurrency architectures.

What std::async Is Good At

It is still useful when you want a small, simple future-producing task with minimal code.

cpp
1#include <future>
2#include <iostream>
3
4int square(int x) {
5    return x * x;
6}
7
8int main() {
9    auto f = std::async(std::launch::async, square, 12);
10    std::cout << f.get() << '\n';
11}

For simple task-based code, that is elegant. The limitation is not that std::async is useless. The limitation is that it is less controllable and less expressive than a serious execution framework.

A Better Mental Model

Think of std::async as a convenience wrapper for launching work with a future result, not as the final answer to structured concurrency or scalable scheduling.

That framing makes Stroustrup’s criticism easier to understand. The complaint is not “this feature never works.” It is “this feature does not give enough control or predictability for broader concurrency design.”

Common Pitfalls

  • Assuming std::async without a policy always creates a new thread immediately.
  • Treating deferred execution as if it were true background parallelism.
  • Using std::async where a thread pool or executor model is really needed.
  • Building large concurrency designs around a convenience API that provides little scheduling control.
  • Missing that implementation freedom can create portability differences in observed behavior.

Summary

  • The main limitation of std::async is lack of control and predictability in task scheduling.
  • The default launch policy may choose deferred execution instead of real async execution.
  • Explicit launch policies help, but they do not turn std::async into an executor framework.
  • 'std::async is useful for simple future-based tasks, not as a complete concurrency architecture.'
  • Stroustrup’s criticism is mainly about execution-model limitations, not about basic usability.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.