async def returns a coroutine; await suspends it until the awaited future completes
Calling an async def function does not run the body; it returns a coroutine object. The body runs when the coroutine is awaited or scheduled on an event loop. await takes an awaitable, such as a coroutine, a Task, or a Future, and suspends the current coroutine until that awaitable completes, then resumes it with the result or raises its exception. Suspension only happens if the awaited object is not already done; if it is done, await returns immediately without yielding to the loop. This is why await is a suspension point, not a thread switch: control returns to the event loop, which then runs other ready tasks. You can only use await inside async def, and async generators use async for and async with instead.
Regular def: runs immediately and returns the result. async def: returns a coroutine, runs when awaited or scheduled.
await obj: suspend until obj completes. Works with coroutines, Futures, and any object implementing await.
asyncio.create_task schedules a coroutine concurrently; await runs it as part of the current flow.
asyncio.gather runs awaitables concurrently and returns results in order.
Trade-off: async functions compose only with async callers. Mixing sync code requires explicit offloading via to_thread or executors.
Common mistake: calling an async function without await and getting a coroutine object that never runs, plus a RuntimeWarning.
Common mistake: assuming await creates concurrency. Awaiting sequentially runs tasks one at a time; you need create_task or gather.
Version note: the RuntimeWarning for never-awaited coroutines has been in 3.x for years. asyncio.to_thread was added in 3.9.
0-2 years experience
2-5 years experience
5-8 years experience
8+ years experience