A strong answer
Production asyncio relies on three concurrency invariants: (1) Keep strong references to background tasks—the event loop only holds weak references to tasks, so fire-and-forget create_task() must add the task to a background_tasks: set[Task] with task.add_done_callback(background_tasks.discard). (2) Structured Concurrency (async with asyncio.TaskGroup() as tg:) guarantees that no child task outlives the async with block: if any child task raises an exception, TaskGroup automatically cancels all sibling tasks and waits for their finally blocks to finish before raising an ExceptionGroup (unlike asyncio.gather, which leaves orphan sibling tasks running in the background when one task fails!). (3) Bounded Backpressure (asyncio.Semaphore(k) or asyncio.Queue(maxsize=M)) caps in-flight concurrency so 50,000 jobs run at most k = 50 at a time.
Exiting an async with asyncio.TaskGroup() block guarantees that every child task spawned inside the group has reached a terminal state (completed, failed, or cancelled and cleaned up) before execution continues past the block. When a child task in a TaskGroup raises an unhandled exception (or the parent task is cancelled), the group immediately calls .cancel() on all remaining active child tasks (injecting asyncio.CancelledError at their current await point) and awaits their completion before propagating.
Capping concurrency with asyncio.Semaphore(50) or Queue(maxsize=100) bounds peak memory and socket descriptors to $O(k)$ instead of $O(N)$ across millions of items. In production systems (FastAPI/Starlette lifespan task groups, httpx connection pool limits, anyio nurseries, and vLLM's async request engine all enforce structured task groups and bounded semaphores.), engineers monitor these exact trade-offs to keep p99 latency predictable and prevent resource exhaustion under peak load.
What the interviewer is listening for
- Traces how structured concurrency (
asyncio.TaskGroup),CancelledError, and bounded backpressure operates in memory step by step rather than giving a vague textbook definition. - States the core correctness invariant and explains why edge cases do not violate it.
- Quantifies time, space, and hardware costs (Unbounded
gather(*[fetch() for _ in 50k]),TaskGroup+Semaphore(50)) with concrete numbers. - Identifies real-world failure modes (Fire-and-forget
asyncio.create_task(coro())without saving the returnedTaskreference) and explains how production systems mitigate them.
Common mistakes
- Treating structured concurrency (
asyncio.TaskGroup),CancelledError, and bounded backpressure as a black box without explaining the underlying memory or state transitions. - Ignoring worst-case tail latency or memory overhead (Catching
CancelledError(orBaseException) and not re-raising it). - Forgetting boundary conditions and error recovery (Performing an
await db.rollback()inside afinally:block when the task is already being cancelled).