Interview frequency: …
Short answer
The event loop manages tasks and runs each coroutine until it reaches an await on an operation that is not ready, then switches to another ready task, all within a single thread.
Explanation
asyncio suits thousands of concurrent network operations at low cost, because tasks voluntarily yield control at await. But any blocking call (such as time.sleep, requests.get or heavy computation) inside a coroutine freezes all the other tasks. Use asynchronous libraries (such as httpx or aiohttp), or move the blocking code to a thread with asyncio.to_thread. The usual entry point is asyncio.run.
Code example
import asyncio
async def fetch(name: str, delay: float) -> str:
await asyncio.sleep(delay) # Yields to the loop
return name
async def main():
results = await asyncio.gather(fetch("a", 1), fetch("b", 1))
print(results) # After about one second, not two
asyncio.run(main())Common mistakes
- Using time.sleep or requests inside async def, which stops the entire event loop.
- Calling a coroutine without await and getting a "never awaited" warning.
Follow-up questions
- What is the difference between a coroutine, a Task and a Future in asyncio?
- How do you test asynchronous (async) code?