Technical
Async Python for Real Workloads: When It Helps and When It Hurts
Async Python has a reputation for being faster than regular Python. The reputation is half true and half misleading. Async helps with one specific thing. Outside that thing, you get complexity without a payoff. Here is the mental model I ended up with.
What Async Actually Does
Async does not make your code faster. It makes your code able to wait on multiple things at the same time. If your code waits on the network, waits on the disk, or waits on other processes, async lets you have many waits in flight at once.
If your code is CPU-bound, async does nothing. Adding async keyword to a number-crunching function does not parallelize it. You get the same speed with more complexity.
Where It Shines
HTTP clients making many calls. Database operations with high latency. WebSocket servers. Any application where most time is spent waiting. FastAPI's popularity is not an accident: web APIs spend most of their time waiting on databases and external services.
Where It Hurts
Data processing scripts that grind through records. Image or video transformations. Anything using NumPy or pandas meaningfully. These are CPU-bound and async only adds ceremony.
# Async wins: 100 concurrent API calls in the time of one
import asyncio, httpx
async def fetch_all(urls):
async with httpx.AsyncClient() as client:
return await asyncio.gather(*[client.get(u) for u in urls])
# Async is pointless here: CPU-bound work
async def process_image(img): # the async keyword is theater
result = expensive_pixel_manipulation(img)
return resultThe Gotcha Nobody Warns You About
Mixing async and sync code is where bugs live. A sync function called from async code blocks the event loop. You think you are running concurrently, you are actually running serially, and nothing errors to warn you.
I now default to async all the way down or sync all the way down in any given module. Mixing is possible but requires attention. For the rare case where I must call blocking code from async, asyncio.to_thread is the escape hatch.
The Decision Rule
If your app is a web API or a worker doing network I/O, go async. If your app is a script that processes data in memory, stay sync. Do not use async because it sounds modern. Use it because your workload waits on I/O.
See the Python asyncio documentation for the mechanics. The discipline of matching the tool to the workload is where real productivity comes from.
RELATED READING
The Consulting Shift I Am Making In Year Two
After a year of writing and building, my consulting practice is changing shape. Shorter engagements. Sharper outcomes.
ReadThe Frontend Shift: Shipping Less JavaScript In Year Two
A year ago I reached for Next.js for everything. This year I often reach for nothing.
ReadThe Serverless Lesson I Would Write On A Sticky Note
After a year of shipping serverless projects, one rule explains most of the wins and all of the losses.
Read