TRT
Sign in
easyAdvancedconcept

Walk through what happens under the hood when an `asyncio` coroutine executes `await client.get(url)`—how do Coroutines, Futures, Tasks, and `epoll` interact.

Trace `Task.__step()` calling `coro.send(None)`, yielding a `Future` up the `await` chain, `loop.add_reader(fd)` on `epoll`, `Task.__wakeup`, and `asyncio.to_thread` / `ProcessPoolExecutor`.

TRT practice prompt — not a verified question from a named employer.

The Runtime Theory Team2 min read

A strong answer

An async def function is a native coroutine built on the exact same heap-allocated frame machinery as a generator (PyCoroObject, driven via .send(None)). The asyncio Event Loop runs on a single OS thread using three components: (1) Future: A state box (PENDING -> FINISHED) holding a list of done-callbacks. (2) Task: A wrapper around a top-level coroutine whose Task.__step() method calls coro.send(None). When the coroutine hits await sock_read(), the bottom-level I/O function creates a Future, registers the socket file descriptor with the OS kernel's epoll (Linux) / kqueue (macOS) multiplexer via loop.add_reader(fd, future.set_result), and yields that Future all the way up to Task.__step()! Task.__step() attaches self.__wakeup as a callback on the Future and returns control to the event loop immediately so other ready tasks can run. (3) When the OS kernel signals that fd is readable, the event loop calls future.set_result(), which schedules Task.__wakeup -> Task.__step(), resuming the coroutine right after the await!

A single-threaded asyncio event loop can switch between tasks ONLY at explicit await expressions that yield control back to the loop; any synchronous blocking call (time.sleep, requests.get, heavy CPU math) monopolizes the single OS thread and halts all other tasks. Because asyncio uses cooperative multitasking on one OS thread without preemptive timer interrupts, loop._run_once() cannot dequeue the next ready callback from loop._ready until the currently executing Task.__step() call returns.

An asyncio.Task + coroutine frame consumes only ~2–4 KB of RAM (compared to 8 MB default virtual stack for an OS thread) and switches in ~150–300 nanoseconds in user space without kernel thread context switches. In production systems (uvloop (libuv Cython event loop), Uvicorn, FastAPI, aiohttp, httpx.AsyncClient, and asyncpg (which parses PostgreSQL wire protocol directly in Cython over non-blocking sockets) all run on this exact Task.__step + epoll architecture.), 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 the asyncio Event Loop, Task.__step trampoline, and OS epoll/kqueue readiness polling 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 (OS Thread (threading.Thread), asyncio.Task + Coroutine) with concrete numbers.
  • Identifies real-world failure modes (Calling asyncio.create_task(coro) as a 'fire-and-forget' call without saving a strong reference to the returned Task) and explains how production systems mitigate them.

Common mistakes

  • Treating the asyncio Event Loop, Task.__step trampoline, and OS epoll/kqueue readiness polling as a black box without explaining the underlying memory or state transitions.
  • Ignoring worst-case tail latency or memory overhead (Calling requests.get(), time.sleep(), or synchronous boto3/psycopg2 inside async def).
  • Forgetting boundary conditions and error recovery (Writing for url in urls: res = await fetch(url) thinking await makes the loop parallel).

This answer walks

Practice follow-ups

  1. 01What is the difference between calling `coro = fetch(url)` (or `await fetch(url)`) vs calling `task = asyncio.create_task(fetch(url))`?
  2. 02Why does `asyncio.create_task(bg_job())` without saving the returned `Task` reference in a `set` risk having the running task garbage-collected mid-execution?!

Not started

Sign in to save your learning progress.

Sign in to save