Concurrency
asyncio Basics
async/await syntax and the asyncio framework — writing concurrent code using coroutines, tasks, and the event loop for I/O-bound operations.
Interview: Modern Python concurrency — frequently asked in backend and systems design interviews.
asyncio is Python's standard library framework for writing concurrent code using the async/await syntax. It's designed for I/O-bound tasks — network requests, database queries, file I/O — where the program spends most time waiting. Unlike threading, asyncio uses cooperative multitasking: coroutines explicitly yield control, avoiding race conditions inherent in preemptive threading.
Core Concepts
- Coroutine: A function defined with
async def— can pause execution withawait - Event Loop: The runtime that schedules and runs coroutines
- Task: A scheduled coroutine —
asyncio.create_task()runs coroutines concurrently - Future: A placeholder for a result that will be available later
asyncio.run()— the main entry point that creates an event loop and runs a coroutine
async/await Syntax
async defdefines a coroutine function (not a regular function)awaitpauses the current coroutine until the awaited operation completes- You can only use
awaitinside anasync deffunction - Calling an async function without
awaitreturns a coroutine object (doesn't execute it)
Running Concurrent Tasks
asyncio.gather(*coros)— run multiple coroutines concurrently, collect all resultsasyncio.create_task(coro)— schedule a coroutine to run in the backgroundasyncio.wait(tasks)— wait for tasks with options (FIRST_COMPLETED, ALL_COMPLETED)asyncio.as_completed(tasks)— iterate over results as they finish
When to Use asyncio
- HTTP clients making many requests (aiohttp, httpx)
- Database queries (asyncpg, aiosqlite)
- WebSocket servers and chat applications
- Web frameworks like FastAPI and Starlette
- Any scenario with many concurrent I/O operations
Interview Insight
Explain that asyncio is single-threaded — concurrency comes from cooperative multitasking, not parallelism. It's ideal for I/O-bound workloads with many concurrent operations. Compare with threading (preemptive, GIL-limited) and multiprocessing (true parallelism for CPU-bound work).
Common Pitfall
Never call blocking functions (time.sleep, requests.get) inside async code — it blocks the entire event loop. Use async alternatives (asyncio.sleep, aiohttp) or run blocking code in an executor.
Use Cases
Web scraping — concurrent HTTP requests to multiple pages
API aggregation — calling multiple microservices simultaneously
Real-time applications — WebSocket servers, chat systems
Async frameworks — FastAPI, Starlette, aiohttp servers
Batch I/O operations — sending emails, writing to databases concurrently
Common Mistakes
Forgetting await — calling async function without await returns a coroutine, not a result
Using blocking calls (time.sleep, requests.get) inside async — blocks the event loop
Creating coroutines without scheduling — use create_task() or gather() to run concurrently
Not handling exceptions in gather — one failure cancels all tasks unless return_exceptions=True
Mixing sync and async code — use asyncio.to_thread() or run_in_executor() for blocking calls