Concurrency
Coroutines
Coroutine functions and cooperative multitasking — async def functions that can pause and resume, enabling non-blocking concurrent execution.
Interview: Core async concept — understanding coroutines is essential for modern Python development.
Coroutines are functions defined with async def that can pause their execution at await points and resume later. Unlike generators (which yield values), coroutines yield control back to the event loop, allowing other coroutines to run while waiting for I/O. They're the building blocks of Python's asyncio framework.
Coroutine Lifecycle
- Created: Calling an async function returns a coroutine object (not yet running)
- Running: The event loop executes the coroutine until it hits
await - Suspended: At
await, control returns to the event loop - Resumed: When the awaited operation completes, the coroutine continues
- Completed: The coroutine returns a value or raises an exception
Awaitable Objects
- Coroutines:
async deffunctions — the most common awaitable - Tasks: Coroutines wrapped with
asyncio.create_task() - Futures: Low-level objects representing eventual results
- You can
awaitany of these inside anasync deffunction
Coroutine Patterns
- Chaining: Coroutines can await other coroutines, building complex workflows
- Nesting:
awaitcan appear in expressions —result = await fetch() + await compute() - Async context managers:
async withfor resources that need async setup/teardown - Async iterators:
async forfor streaming data
Interview Insight
Coroutines enable cooperative multitasking — they voluntarily yield control, unlike threads which are preempted. This eliminates many concurrency bugs (no lock needed for shared state modification between await points). Mention that coroutines have near-zero overhead compared to OS threads.
Use Cases
Async pipelines — fetch → transform → store workflows
Streaming data — async iterators for real-time data feeds
Resource management — async context managers for DB connections, file handles
Timeout handling — asyncio.timeout() for bounded async operations
Task cancellation — gracefully stopping long-running operations
Common Mistakes
Forgetting await — coroutine objects are silently created but never executed
Using time.sleep() instead of asyncio.sleep() — blocks the entire event loop
Not handling CancelledError — tasks should clean up resources when cancelled
Creating too many coroutines at once — use asyncio.Semaphore to limit concurrency
Trying to await in __init__ — constructors cannot be async; use factory methods instead