Skip to main content
PEP 492 · Speedrun · Python 3.5 (2015)

PEP 492 in 90 seconds — async/await and the death of @coroutine

Concurrent IO without threads, callbacks, or futures soup.

Pre-3.5, doing async in Python meant `@asyncio.coroutine` decorator + `yield from` for every await point. Easy to forget the decorator, easy to mix sync and async by accident, and the IDE had no idea what was happening: ```python @asyncio.coroutine def fetch_user(uid): response = yield from http.get(f"/users/{uid}") return response.json() ``` PEP 492 (Python 3.5, 2015) added two dedicated keywords: `async def` for the coroutine, `await` for suspension. The runtime semantics are the same; the syntax tells the parser, IDE, and reader "this is async" explicitly.

Once `async def`/`await` shipped, the ecosystem could finally lean in: `asyncio.gather()` to run things in parallel, `async with` for context managers, `async for` (PEP 525) for streaming. Every modern async library is built on this two-keyword foundation — `httpx`, `aiohttp`, `asyncpg`, FastAPI, the LLM SDK streaming endpoints, all of them.

Before — Python 3.4, @coroutine + yield from
import asyncio

@asyncio.coroutine
def slow_double(x):
    yield from asyncio.sleep(0)
    return x * 2

@asyncio.coroutine
def main():
    a = yield from slow_double(3)
    b = yield from slow_double(5)
    return a + b

asyncio.get_event_loop().run_until_complete(main())
After — Python 3.5+, async/await
import asyncio

async def slow_double(x):
    await asyncio.sleep(0)
    return x * 2

async def main():
    # Sequential: a then b
    a = await slow_double(3)
    b = await slow_double(5)

    # Or parallel — gather runs both concurrently
    a2, b2 = await asyncio.gather(slow_double(3), slow_double(5))

    return a + b

asyncio.run(main())   # Python 3.7+ entry-point shortcut

🎯 Predict the output

What does this print? `asyncio.gather(*coros)` runs the coroutines concurrently and returns their results in input order.

import asyncio

async def slow_double(x):
    await asyncio.sleep(0)
    return x * 2

async def main():
    result = await asyncio.gather(slow_double(3), slow_double(5), slow_double(7))
    print(result)
    print(sum(result))

asyncio.run(main())
async/await basics → Foundations track

Or speedrun another PEP

PEP 572Assignment as an expression — 3 lines become 1.
PEP 572 in 90 seconds — the walrus that ate your for-loop
PEP 484Python stays dynamic — but you can opt into typing.
PEP 484 in 90 seconds — type hints, without TypeScript-envy
PEP 622Switch statements are a 5% feature. Pattern matching is the 95%.
PEP 622 in 90 seconds — match / case isn't just a switch