1. Event loop, coroutines and tasks
Start with the contract: make inputs, outputs and failure behavior explicit before adding infrastructure. This keeps the feature easy to reason about and gives tests a stable boundary.
2. I/O-bound versus CPU-bound work
Apply the pattern through a small vertical slice. Keep framework wiring at the edge and business decisions in focused functions or services that can be tested without starting the whole application.
async def dashboard(user_id: int):
profile, tasks = await asyncio.gather(
users.get(user_id),
task_repo.list_for_user(user_id),
)
return {"profile": profile, "tasks": tasks}3. Threads, processes and task queues
Treat failure paths as part of the design. Add bounded resource usage, meaningful errors and a verification step so the behavior remains dependable under real production conditions.
4. Hands-on Exercise
Build the feature
Call two independent I/O services sequentially and concurrently, measure both versions, then move a CPU-heavy report out of the event loop.
Definition of done
- The happy path works through the real HTTP boundary.
- At least one failure path is handled and tested.
- Configuration and secrets stay outside source code.
- The README explains how to run and verify the result.
5. Knowledge Check
What happens when blocking I/O runs directly inside async def?
Show answer
It blocks the event-loop thread, preventing unrelated requests from progressing until that call returns.