A strong answer
Production asyncio enforces two invariants: (1) Bounded Backpressure (asyncio.Semaphore(limit) or asyncio.Queue(maxsize=N)): Never fan out an unbounded input list directly into tasks; acquire a Semaphore(50) inside each worker (or use a fixed pool of 50 worker tasks draining a bounded Queue) so at most 50 requests are in flight at once. (2) Structured Concurrency (async with asyncio.TaskGroup() as tg: in Python 3.11+): Unlike gather, a TaskGroup bounds the lifetime of all child tasks to the lexical async with block. If any child task raises an unhandled exception, the TaskGroup immediately cancels all remaining sibling tasks (by injecting asyncio.CancelledError at their current await point), waits for every cancelled sibling's finally: cleanup block to finish, and only then exits the async with block raising an ExceptionGroup!
No task spawned inside an async with asyncio.TaskGroup() as tg: block is ever still running after execution exits the async with block—whether via normal completion, child exception, or parent cancellation. Because TaskGroup.__aexit__ intercepts the first failing child task, calls .cancel() on every other active child in its set, and awaits their termination before propagating the ExceptionGroup, orphan background tasks are structurally impossible.
Capping fan-out concurrency with asyncio.Semaphore(64) keeps open sockets, TLS buffers, and memory bounded (O(limit) instead of O(N)), while TaskGroup guarantees zero leaked tasks on error unwind. In production systems (AnyIO, Trio (which pioneered Structured Concurrency in Python), FastAPI lifespan/background task groups, and asyncio.timeout(5.0) (Python 3.11+) all build on scoped cancellation and TaskGroup semantics.), 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, andasyncio.Semaphorebackpressure 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(*10k_coros),TaskGroup+Semaphore(50)) with concrete numbers. - Identifies real-world failure modes (Swallowing
asyncio.CancelledErrorin atry ... except BaseException:block without re-raising) and explains how production systems mitigate them.
Common mistakes
- Treating Structured Concurrency (
asyncio.TaskGroup),CancelledError, andasyncio.Semaphorebackpressure as a black box without explaining the underlying memory or state transitions. - Ignoring worst-case tail latency or memory overhead (Performing an
awaitinside afinally:block when the task is already being cancelled, without knowing that theawaitwill only work if it doesn't itself get cancelled). - Forgetting boundary conditions and error recovery (Spawning 1,000,000 tasks upfront into a
TaskGroupguarded by aSemaphore(50)).