async / await

Calling an async def function does not run it — it creates a coroutine. await runs a coroutine to completion, letting the event loop run other tasks whenever it has to wait.

FunctionsPython 3.5+Live demo
async def / await
async def main():
    result = await coro()
run it
asyncio.run(main())
async for
async for item in aiter_obj:
    ...
async with
async with lock:
    ...
Use for
Many concurrent waits (network, sockets, subprocesses) in one thread
Result
async def → a coroutine function; await x → the result of the awaitable x
Pairs with
asyncio.run(), asyncio.gather(), asyncio.create_task(), async for, async with
Watch out
forgetting await gives you a coroutine object, not a result (and a "never awaited" warning)

Demo

Live evaluation
Two awaits one after the other: b does not start until a has finished, even though a keeps yielding to the loop.
Try:
Inputs
xslista's items
yslistb's items
Code
import asyncio
xs = [1, 2, 3]
ys = [4, 5]
log = []
async def worker(name, items):
    for x in items:
        log.append(f'{name}{x}')
        await asyncio.sleep(0)
async def main():
    await worker('a', xs)
    await worker('b', ys)
asyncio.run(main())
log
Result
['a1', 'a2', 'a3', 'b4', 'b5']

Compare the first two tabs with the same inputs. Plain sequential awaits give all of a, then all of b. With gather, both workers are tasks on the event loop and each await asyncio.sleep(0) lets the other one take a step, so the log alternates a, b, a, b until the shorter one runs out. That order is deterministic: the loop runs ready tasks first in, first out.

Syntax slots

NameTypeRequiredDescription
async defdefinitionyesDefines a coroutine function. await, async for and async with are only allowed inside one.
await exprexpressionnoexpr must be awaitable (coroutine, Task, Future). Suspends this coroutine until the result is ready.
async forstatementnoLoops over an asynchronous iterable (__aiter__ / __anext__), e.g. an async generator.
async withstatementnoUses an asynchronous context manager (__aenter__ / __aexit__), e.g. asyncio.Lock or an HTTP session.

Common patterns

Entry point
One asyncio.run() at the top; everything below it is async.
import asyncio

async def main():
    ...

if __name__ == "__main__":
    asyncio.run(main())
Run several things concurrently
gather returns results in argument order, whatever finished first.
async def main():
    results = await asyncio.gather(fetch(a), fetch(b), fetch(c))
TaskGroup (3.11+)
Structured concurrency: if one task fails, the others are cancelled.
async def main():
    async with asyncio.TaskGroup() as tg:
        t1 = tg.create_task(fetch(a))
        t2 = tg.create_task(fetch(b))
    print(t1.result(), t2.result())
Timeout (3.11+)
Give up on a slow await.
async def load(url):
    async with asyncio.timeout(5):
        return await fetch(url)

Examples

1. Run a coroutine
import asyncio async def add(a, b): return a + b asyncio.run(add(2, 3))
Returns
5
2. Calling it only creates a coroutine
async def f(): return 1 c = f() name = type(c).__name__ c.close() name
Returns
'coroutine'
3. gather keeps argument order
import asyncio async def double(x): await asyncio.sleep(0) return x * 2 async def main(): return await asyncio.gather(double(1), double(5)) asyncio.run(main())
Returns
[2, 10]
4. async with
import asyncio async def main(): lock = asyncio.Lock() async with lock: inside = lock.locked() return inside, lock.locked() asyncio.run(main())
Returns
(True, False)
5. async for over an async generator
import asyncio async def countdown(n): while n: yield n n -= 1 async def main(): return [x async for x in countdown(3)] asyncio.run(main())
Returns
[3, 2, 1]
6. await outside async def
compile('def f():\n await g()', '<demo>', 'exec')
Returns
SyntaxError: 'await' outside async function
7. Is it a coroutine function?
import inspect async def f(): pass inspect.iscoroutinefunction(f)
Returns
True

Pitfalls

1. Forgetting await
Without await you get a coroutine object, not its result — and Python later warns "coroutine ... was never awaited".
no await
import asyncio
async def get():
    return 41
async def main():
    c = get()
    try:
        return c + 1
    finally:
        c.close()
asyncio.run(main())
TypeError: unsupported operand type(s) for +: 'coroutine' and 'int'
await it
import asyncio
async def get():
    return 41
async def main():
    return await get() + 1
asyncio.run(main())
42
2. Calling asyncio.run() inside async code
asyncio.run() starts an event loop; inside a coroutine one is already running. Just await the coroutine.
nested run
import asyncio
async def inner():
    return 1
async def main():
    c = inner()
    try:
        return asyncio.run(c)
    finally:
        c.close()
asyncio.run(main())
RuntimeError: asyncio.run() cannot be called from a running event loop
await
import asyncio
async def inner():
    return 1
async def main():
    return await inner()
asyncio.run(main())
1
3. Sequential awaits when you wanted concurrency
await a(); await b() waits for a before b even starts. Start both and gather them.
one by one
import asyncio
log = []
async def job(name):
    log.append(name + ' start')
    await asyncio.sleep(0)
    log.append(name + ' end')
async def main():
    await job('a')
    await job('b')
asyncio.run(main())
log
['a start', 'a end', 'b start', 'b end']
gather
import asyncio
log = []
async def job(name):
    log.append(name + ' start')
    await asyncio.sleep(0)
    log.append(name + ' end')
async def main():
    await asyncio.gather(job('a'), job('b'))
asyncio.run(main())
log
['a start', 'b start', 'a end', 'b end']

When to use

Use it
  • Waiting on many network or I/O operations at once (HTTP clients, websockets, database drivers with async APIs)
  • Servers handling many connections in one thread
  • Timeouts and cancellation of waits
Reach for something else
  • CPU-heavy work → multiprocessing or concurrent.futures (async does not add CPU parallelism)
  • Libraries with only blocking APIs → threads, or asyncio.to_thread()
  • A short script that does one thing at a time → plain synchronous code

Notes

CPython impl
A coroutine is a suspended frame, like a generator; await delegates to the awaitable much like yield from
Event loop
Nothing runs a coroutine except an event loop (asyncio.run) or another coroutine that awaits it
Blocking
time.sleep() or a blocking call inside a coroutine freezes every task on the loop — use the async equivalent

FAQ

You called an async def function but never awaited the coroutine it returned, so its body never ran. Python emits RuntimeWarning: coroutine 'name' was never awaited when the unused coroutine is garbage-collected. Add await (or pass it to asyncio.run / create_task / gather).

History

3.5
async def, await, async for and async with added (PEP 492).
3.6
Asynchronous generators and asynchronous comprehensions introduced.
3.7
await and async are now keywords; previously they were only treated as such inside the body of a coroutine function.