async def creates a coroutine function
import asyncio
async def say_hi():
await asyncio.sleep(0.1)
return "hi"Calling vs awaiting
Calling a coroutine function returns a coroutine object.
import asyncio
async def say_hi():
await asyncio.sleep(0.1)
return "hi"
async def main():
c = say_hi() # coroutine object (NOT executed yet)
msg = await say_hi() # executed
print(type(c))
print(msg)
asyncio.run(main())await points
You can only use await inside an async def function.
If you need to run async code from normal code, use:
asyncio.run(main())(most common)
Common mistake
This does nothing useful:
async def main():
say_hi() # forgot awaitBecause the coroutine is created but never awaited.
Why coroutines exist
Most programs spend a lot of time waiting: for a web server to answer, a file to load, a database to reply. A normal function that waits blocks everything behind it. A coroutine is a function that can pause while it waits and let other work run, then resume exactly where it left off. You create one by writing async def, and you pause inside it with await.
Reading the two examples
In coroutine_def.py, say_hi waits 0.1 seconds with await asyncio.sleep(0.1) and then returns "hi". The return value is ordinary: when the coroutine is awaited, you get "hi" back just like from a normal function.
In calling_vs_awaiting.py, the important lines are these two:
c = say_hi()builds a coroutine object. The body has not started; you hold a plan, not a result. Python will even warn you that the coroutine was "never awaited".msg = await say_hi()runs the body to completion, pausing at the sleep, and gives you the returned value.
Printing type(c) shows <class 'coroutine'>, which proves nothing ran.
Running two things at once
A plain await runs one coroutine after another. To overlap the waiting, schedule coroutines as tasks or gather them:
import asyncio
import time
async def fetch(name: str, delay: float) -> str:
await asyncio.sleep(delay)
return f"{name} done"
async def main() -> None:
start = time.perf_counter()
results = await asyncio.gather(fetch("A", 0.3), fetch("B", 0.3))
print(results)
print(f"took about {time.perf_counter() - start:.1f}s") # about 0.3, not 0.6
asyncio.run(main())Two 0.3 second waits finish in roughly 0.3 seconds in total, because while A waits, the event loop runs B.
Common mistakes
- Forgetting
await.say_hi()alone does nothing. If your prints never appear, look for a missingawait. - Using
awaitin a normal function. It is aSyntaxErroroutsideasync def. Start the chain withasyncio.run(main()). - Blocking calls inside a coroutine.
time.sleep(1)freezes the whole event loop. Useawait asyncio.sleep(1)instead. - Calling
asyncio.runinside a running loop. In notebooks, where a loop is already running, useawait main()instead.
When to use it
Use coroutines for I/O-bound work with lots of waiting: many network requests, chat servers, scraping. They do not speed up heavy calculation; for that, use processes.
🧪 Try It Yourself
Exercise 1 – Your First Coroutine
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.Exercise 2 – await asyncio.sleep
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.Exercise 3 – Gather Two Coroutines
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.