Skip to the solution
HardDSA12 min read

How does async/await work in Python?

141 views

The problem

Question presented to candidate: "Explain what actually happens when you write async def and await in Python. What does calling an async function return, what does await do under the hood, and why can one slow line freeze an entire asyncio program?"

What a strong answer should cover:

  • async def defines a coroutine function; calling it runs nothing and returns a coroutine object that must be awaited or wrapped in a task, otherwise you get RuntimeWarning: coroutine ... was never awaited.
  • await x works on any awaitable (a coroutine, a Task, a Future, or an object with __await__). It suspends the current coroutine and the whole chain of awaiting coroutines up to the driver, which resumes it later with send(); the return value travels on StopIteration.
  • Coroutines are built on the same machinery as generators (RETURN_GENERATOR, SEND, YIELD_VALUE in the bytecode); the event loop is just the code that calls send() at the right time.
  • Concurrency is cooperative and single-threaded: await on a coroutine runs it to completion sequentially; to overlap work you create tasks (asyncio.create_task, gather, TaskGroup). Nothing runs in parallel.
  • Any blocking call (time.sleep, requests.get, CPU-heavy loops) blocks the loop and every task; use async libraries, asyncio.to_thread, or a process pool.
  • Version facts: async/await syntax came with PEP 492 in 3.5, became reserved keywords in 3.7, and asyncio.run() was added in 3.7.

Code / implementation expected: Optional β€” a small asyncio program comparing sequential awaits with gather, or a coroutine driven by hand with send().

  • async
  • asyncio

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

12 min read Β· 13 sections

Run the code

Edit the code and run it here. Each console line with an expected value becomes a test.

PythonDriving coroutines by hand, no asyncio
PythonReal asyncio: sequential vs concurrent, and a blocking call
# Real asyncio: sequential awaits vs concurrent tasks, and a blocking call.
# asyncio.run() cannot run inside the browser page, so this runs on CPython.
import asyncio
import time

log = []


async def download(name, seconds):
    log.append(f"start {name}")
    await asyncio.sleep(seconds)          # yields to the loop while "waiting"
    log.append(f"done  {name}")
    return name.upper()


async def sequential():
    a = await download("a", 0.2)          # await = wait for THIS one to finish
    b = await download("b", 0.1)
    return [a, b]


async def concurrent():
    # gather wraps each coroutine in a Task, so both are in flight at once
    return await asyncio.gather(download("a", 0.2), download("b", 0.1))


async def ticker(stop):
    ticks = 0
    while not stop.is_set():
        ticks += 1
        await asyncio.sleep(0.05)
    return ticks


async def blocking_demo():
    stop = asyncio.Event()
    t = asyncio.create_task(ticker(stop))
    await asyncio.sleep(0)                # let the ticker take its first step
    before = time.perf_counter()
    time.sleep(0.3)                       # BLOCKS the thread: no task can run
    stalled = round(time.perf_counter() - before, 1)
    stop.set()
    ticks = await t
    return stalled, ticks


for runner in (sequential, concurrent):
    log.clear()
    start = time.perf_counter()
    result = asyncio.run(runner())
    elapsed = round(time.perf_counter() - start, 1)
    print(f"{runner.__name__:10} -> {result} in ~{elapsed}s")
    for line in log:
        print("   ", line)

stalled, ticks = asyncio.run(blocking_demo())
print(f"time.sleep blocked the loop for ~{stalled}s; ticker ran {ticks} step(s)")

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 138 of 139 in Python

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Python questions
Up nextHard

What is Python 3.12's free-threaded (no-GIL) build and what changes?

Continue