C++

Coroutines

10 question(s)

What is a coroutine in C++20?

Beginner
A coroutine is a function that can suspend its execution and resume later, preserving its local state across suspensions. A function becomes a coroutine if its body uses co_await, co_yield, or co_return. Coroutines enable writing asynchronous or lazy/generator code in a sequential, readable style instead of callbacks or state machines.
Generator<int> counter() {
    for (int i = 0;; ++i) co_yield i;   // suspends, yielding i
}
Real-world example An async network handler is written as a coroutine that co_awaits I/O, reading like straight-line code.

Common follow-ups: What three keywords make a coroutine? | What do coroutines enable?

Concurrency Coroutines Coroutines

What do co_await, co_yield, and co_return do?

Beginner
co_await suspends the coroutine until an awaited operation is ready, then resumes. co_yield suspends and produces a value to the caller (used for generators), resuming on the next request. co_return ends the coroutine, optionally returning a value. These keywords drive the suspend/resume behavior.
co_yield x;    // produce a value, suspend
co_await task; // wait for async result
co_return r;   // finish
Real-world example A lazy sequence generator uses co_yield to produce one element per resumption on demand.

Common follow-ups: Which keyword produces a value lazily? | Which awaits an async result?

Coroutines Concurrency Coroutines

What is the difference between a coroutine and a regular function?

Intermediate
A regular function runs to completion in one invocation using a single stack frame that's destroyed on return. A coroutine can suspend and resume, so its state (locals, execution point) is stored in a separate 'coroutine frame' (often heap-allocated) that persists across suspensions until it completes or is destroyed. This lets it pause without blocking a thread.
Real-world example Unlike a normal function, a coroutine pauses at co_await and lets the thread do other work meanwhile.

Common follow-ups: Where is coroutine state stored? | Why can a coroutine pause without blocking?

Memory Management Concurrency Coroutines

What is a generator and how do coroutines implement one?

Intermediate
A generator is a function producing a sequence of values lazily, one at a time, on demand. With coroutines, co_yield suspends and hands back a value each time the consumer asks for the next, resuming where it left off. This avoids computing or storing the whole sequence, enabling infinite or expensive sequences to be consumed incrementally.
Generator<int> fib() {
    int a=0,b=1;
    while (true) { co_yield a; auto t=a; a=b; b=t+b; }
}
Real-world example An infinite Fibonacci generator yields numbers lazily so the consumer takes only as many as needed.

Common follow-ups: Why is a generator memory-efficient? | Which keyword yields values?

Ranges & Views Coroutines Coroutines

What are the coroutine machinery components (promise_type, awaitable, coroutine_handle)?

Advanced
A coroutine's return type must provide a promise_type controlling its behavior (initial/final suspend, how co_yield/co_return/exceptions are handled, and how the return object is built). An awaitable (with await_ready/await_suspend/await_resume) defines what co_await does. coroutine_handle is a low-level handle to resume/destroy the coroutine. The standard library provides little out of the box before C++23's std::generator, so libraries or hand-written types supply these.
struct promise_type {
    auto get_return_object();
    std::suspend_always initial_suspend();
    std::suspend_always final_suspend() noexcept;
    void return_void(); void unhandled_exception();
};
Real-world example A library author writes a Task type with a promise_type to integrate coroutines with its event loop.

Common follow-ups: What does promise_type control? | What is an awaitable?

Templates Coroutines Coroutines

What are the performance and memory considerations of coroutines?

Advanced
Coroutine frames are typically heap-allocated (though the compiler may elide the allocation—HALO optimization—when the lifetime is bounded and visible). Suspension/resumption is cheap versus thread context switches. Concerns include the allocation cost, potential for many live frames, and ensuring awaited objects/captured references outlive suspension. They excel at high-concurrency I/O without a thread per task.
Real-world example An async server handles thousands of connections with coroutines instead of thousands of OS threads, saving memory.

Common follow-ups: What is HALO (allocation elision)? | Why are coroutines cheaper than threads for I/O?

Memory Management Concurrency Coroutines

What are typical use cases for coroutines?

Beginner
Coroutines suit asynchronous I/O (network/file operations written sequentially), lazy generators/sequences, cooperative multitasking, state machines, and streaming/pipeline processing. They make code that would otherwise use callbacks, futures chaining, or explicit state machines read like ordinary sequential code.
Real-world example An asynchronous download is written as a coroutine that co_awaits each chunk, avoiding nested callbacks.

Common follow-ups: Why are coroutines good for async I/O? | What do they replace?

Concurrency Coroutines Coroutines

What is std::generator (C++23) and how does it simplify coroutine use?

Advanced
std::generator (C++23) is a standard library coroutine return type for writing generators—so you can co_yield values without hand-writing a promise_type and iterator machinery. It provides a ready-made, range-compatible lazy sequence type, making the common generator use case simple and portable, unlike C++20 where you needed a custom or third-party type.
std::generator<int> squares(int n){
    for (int i=0;i<n;++i) co_yield i*i;
}
Real-world example With std::generator, producing a lazy sequence needs only co_yield, no boilerplate coroutine plumbing.

Common follow-ups: What boilerplate does std::generator remove? | Is std::generator range-compatible?

Ranges & Views Modern C++ Coroutines

What are common pitfalls when using coroutines?

Intermediate
Pitfalls include dangling references: a coroutine that captures or references locals/parameters by reference can access them after the caller's frame is gone across a suspension; passing parameters by reference to a coroutine that outlives them is dangerous. Also, forgetting to keep the coroutine handle alive, or awaiting objects that don't outlive the suspension, cause use-after-free. Prefer passing owned/by-value data into coroutines.
// dangerous: reference parameter may dangle after suspension
Task process(const std::string& ref);  // prefer by value/owning
Real-world example A coroutine referencing a temporary argument crashes after suspension; passing by value fixes it.

Common follow-ups: Why can coroutine reference parameters dangle? | How do you avoid it?

Memory Management Coroutines Coroutines

How do coroutines differ from threads?

Beginner
Threads are OS-scheduled, run truly in parallel (on multiple cores), and each has its own stack with relatively high memory and context-switch cost. Coroutines are cooperatively scheduled within a thread—they suspend/resume at explicit points, are extremely lightweight, and don't provide parallelism by themselves. Coroutines handle concurrency (many tasks) efficiently; threads provide parallelism.
Real-world example One thread runs thousands of coroutines cooperatively, versus the high cost of thousands of OS threads.

Common follow-ups: Do coroutines run in parallel by themselves? | Which is more lightweight?

Concurrency Coroutines Coroutines