Async shines for I/O-bound workloads (HTTP, DB, files) where one task waits for I/O most of the time. It lets ONE thread drive thousands of concurrent tasks because each awaits others. CPU-bound work? Use multiprocessing or ProcessPoolExecutor — async doesn’t speed up Python’s GIL-bound pure compute.
async def fetch(url): … reads in parallel for 100 URLs in ~1 RTT.
async with async_session() -> async for row in session.execute(…): … for high-throughput DB.
asyncio.gather(*tasks) fans out and waits for the slowest.
CPU-heavy work -> concurrent.futures.ProcessPoolExecutor, NOT asyncio.
I/O wait -> async/await (concurrency)
CPU compute -> multiprocessing (parallelism in separate processes)
async for I/O concurrency. multiprocessing for CPU parallelism. Don’t mix the two.