hardBackend EngineerTechnology
How does Python's async/await work under the hood, and when should you use asyncio vs threading vs multiprocessing?
Posted 18/04/2026
by Mehedy Hasan Ador
Question Details
At a web scraping company:
"We need to scrape 10,000 URLs. Using threading with 50 workers takes 5 minutes. Using asyncio with aiohttp takes 45 seconds. Why is asyncio so much faster here?"
Suggested Solution
async/await Under the Hood
Python's asyncio uses a single-threaded event loop (similar to Node.js):
import asyncio
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def main():
# Run 10,000 requests concurrently
tasks = [fetch(url) for url in urls]
results = await asyncio.gather(*tasks)
asyncio.run(main())
The Event Loop
1. fetch(url1) → starts HTTP request → awaits response → YIELDS control
2. fetch(url2) → starts HTTP request → awaits response → YIELDS control
3. ... 10,000 requests in flight, one thread handling all
4. Response arrives for url1 → resume fetch(url1) → return data
When to Use What
| Approach | Best For | Concurrency | Memory/thread |
|---|---|---|---|
| asyncio | I/O-bound (HTTP, DB, files) | 10K+ concurrent | ~1KB per coroutine |
| threading | I/O-bound + legacy code | ~100 concurrent | ~8MB per thread |
| multiprocessing | CPU-bound (parsing, ML) | CPU cores | ~30MB per process |
Why asyncio Was Faster for Scraping
- Threading: 50 threads × 8MB = 400MB overhead, OS context switching
- asyncio: 10K coroutines × 1KB = 10MB overhead, no context switching
- Result: More concurrent connections, less overhead, faster completion
Common Pitfalls
# ❌ Blocking the event loop
async def handler():
time.sleep(5) # BLOCKS THE ENTIRE EVENT LOOP
# No other coroutines can run for 5 seconds!
# ✅ Non-blocking
async def handler():
await asyncio.sleep(5) # Yields to event loop
# ❌ Running CPU work in async
async def process():
result = heavy_computation(data) # Blocks event loop
# ✅ Offload to process pool
async def process():
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(None, heavy_computation, data)