Async Python: Why Your Programs Are Slow and How to Fix Them
โก Quick Answer
A practical Python async/await tutorial: understand why synchronous code is slow for I/O tasks and how asyncio, async def, and await make programs dramatically faster.
Get more content like this on Telegram!
Daily AI tips, notes & resources โ free
Advertisement
Async Python: Why Your Programs Are Slow and How to Fix Them
I had a script that needed to check the status of 200 URLs and report which ones were down. Synchronous version: 4 minutes. Async version: 8 seconds.
That's not a cherry-picked example. That's typical for I/O-bound work.
Async/await lets one program juggle many waiting tasks at once instead of standing in line for each โ like a waiter taking five tables' orders while food cooks, instead of standing at one table until its meal arrives. This tutorial explains why synchronous Python is slow for I/O-bound tasks like this, and how asyncio fixes it.
Why Your Synchronous Code Is Slow
Consider fetching 5 URLs synchronously:
import requests
import time
urls = [
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/1",
]
start = time.time()
responses = []
for url in urls:
response = requests.get(url)
responses.append(response.status_code)
print(f"Took {time.time() - start:.1f}s")
# Output: Took 5.2sEach request takes ~1 second. With 5 requests: ~5 seconds. With 200 requests: ~200 seconds.
Why? During each request, Python is just waiting โ waiting for the server to respond. Your CPU is idle. Your program is blocked.
This is I/O-bound work: the bottleneck is waiting for external data, not computation.
The Async Solution
import asyncio
import aiohttp
import time
async def fetch(session, url):
async with session.get(url) as response:
return response.status
async def fetch_all(urls):
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
return await asyncio.gather(*tasks)
urls = ["https://httpbin.org/delay/1"] * 5
start = time.time()
results = asyncio.run(fetch_all(urls))
print(f"Took {time.time() - start:.1f}s")
# Output: Took 1.1sSame 5 requests. All running concurrently. Finished in ~1 second instead of ~5.
pip install aiohttpUnderstanding async/await
What async def Does
async def my_coroutine():
return "hello"Adding async to a function makes it a coroutine. Calling it doesn't execute it immediately โ it returns a coroutine object. You have to await it or run it with asyncio.run().
# This does NOT run the function:
result = my_coroutine() # returns a coroutine object
# This DOES run it:
result = await my_coroutine() # only valid inside another async function
# OR
result = asyncio.run(my_coroutine()) # valid at top levelWhat await Does
await tells Python: "Start this operation, and while waiting for it to complete, go do something else."
async def fetch_data():
print("Starting request...")
await asyncio.sleep(1) # Simulate waiting for network
print("Request done!")
return "data"When Python hits await asyncio.sleep(1), instead of blocking for 1 second, the event loop can run other coroutines.
The Event Loop
The event loop is the engine that runs async code โ a single-threaded traffic controller that switches between waiting tasks the instant one of them pauses, rather than a highway with multiple lanes:
async def main():
# Run two coroutines concurrently
result1, result2 = await asyncio.gather(
fetch_data(),
fetch_data(),
)
asyncio.run(main()) # Creates an event loop and runs main()asyncio.gather() โ The Key to Concurrency
asyncio.gather() runs multiple coroutines concurrently and waits for all of them:
import asyncio
async def task(name, duration):
print(f"{name}: starting (will take {duration}s)")
await asyncio.sleep(duration)
print(f"{name}: done")
return f"{name} result"
async def main():
start = time.time()
# Run 3 tasks concurrently
results = await asyncio.gather(
task("Task A", 2),
task("Task B", 1),
task("Task C", 3),
)
print(f"All done in {time.time() - start:.1f}s")
print(results)
asyncio.run(main())
# Output:
# Task A: starting (will take 2s)
# Task B: starting (will take 1s)
# Task C: starting (will take 3s)
# Task B: done
# Task A: done
# Task C: done
# All done in 3.0s โ Not 6s! They ran concurrently.Total time = longest task (3s), not sum of all tasks (6s).
Real-World Example: Fetch Multiple APIs Concurrently
import asyncio
import httpx
import json
async def fetch_user(client: httpx.AsyncClient, user_id: int) -> dict:
response = await client.get(f"https://jsonplaceholder.typicode.com/users/{user_id}")
return response.json()
async def fetch_all_users(user_ids: list[int]) -> list[dict]:
async with httpx.AsyncClient() as client:
tasks = [fetch_user(client, uid) for uid in user_ids]
users = await asyncio.gather(*tasks)
return users
async def main():
user_ids = list(range(1, 11)) # Users 1-10
start = time.time()
users = await fetch_all_users(user_ids)
elapsed = time.time() - start
print(f"Fetched {len(users)} users in {elapsed:.2f}s")
for user in users:
print(f" {user['name']}: {user['email']}")
asyncio.run(main())pip install httpxAsync Database Queries
import asyncio
import aiosqlite
async def get_users():
async with aiosqlite.connect("app.db") as db:
async with db.execute("SELECT id, name FROM users") as cursor:
return await cursor.fetchall()
async def main():
users = await get_users()
for user in users:
print(user)
asyncio.run(main())pip install aiosqliteAsync with FastAPI
FastAPI is built on asyncio. Using async def in route handlers lets FastAPI handle many requests concurrently on a single thread:
from fastapi import FastAPI
import httpx
app = FastAPI()
@app.get("/weather/{city}")
async def get_weather(city: str):
async with httpx.AsyncClient() as client:
response = await client.get(f"https://wttr.in/{city}?format=j1")
return response.json()If your FastAPI route makes database queries or external API calls, use async versions of those libraries. For more on FastAPI, see our FastAPI REST API tutorial.
When NOT to Use Async
CPU-bound tasks: If your code does heavy computation (number crunching, image processing, training ML models), async won't help โ it only helps with I/O waits. Use multiprocessing instead.
# Don't async-ify CPU work:
import multiprocessing
def process_chunk(data):
return sum(x ** 2 for x in data) # CPU-bound
with multiprocessing.Pool() as pool:
results = pool.map(process_chunk, data_chunks)Simple scripts: A one-time script that makes 3 API calls doesn't need async. The overhead of learning and implementing it isn't worth it for trivial concurrency.
Rules of thumb:
- < 10 concurrent I/O operations: synchronous is fine
- 10โ1,000 concurrent I/O: async is valuable
- CPU-heavy tasks: multiprocessing
Common Async Mistakes
Mistake 1: Using sync libraries in async code
# WRONG โ blocks the event loop
async def bad():
response = requests.get(url) # This blocks everything!
# CORRECT
async def good():
async with httpx.AsyncClient() as client:
response = await client.get(url)Mistake 2: Forgetting await
async def fetch():
return await some_async_call() # correct
async def broken():
return some_async_call() # Returns coroutine object, not resultMistake 3: Running event loops inside event loops
# If you're already inside an async context, don't call asyncio.run()
# Use await insteadFurther Reading
- FastAPI Tutorial 2026 โ Build Production-Ready REST APIs with Python
- Python OOP Explained Simply: Classes and Objects for Real Beginners
- Python Automation: 20 Scripts That Will Save You Hours Every Week
- How I Learned Python in 3 Months and Got a Job: My Honest Story
- Python Testing with Pytest: Write Tests That Actually Catch Bugs
- Overfitting in Machine Learning: How to Detect and Fix It
- Terminal and Command Line Mastery: 30 Commands That Changed My Life
- Building a REST API with Node.js and Express for Beginners
Advertisement
๐ฌ DiscussionPowered by GitHub Discussions
Frequently Asked Questions
Problem Solver and Cloud Expert
Solves complex infrastructure challenges and architects reliable, scalable cloud deployments for every project. Shamshur Rahman keeps AiTechWorldsโ hosting and cloud infrastructure fast, resilient, and cost-efficient.
Not sure yet? Ask AI about this article
Get an instant, unbiased AI summary of โAsync Python: Why Your Programs Are Slow and How to Fix Themโ.
Advertisement
Related Articles
The Python Libraries Every Developer Must Know in 2026
The essential Python libraries for 2026: from requests and pandas to FastAPI and LangChain โ what each does, when to use it, and how to get started quickly.
Django vs Flask in 2026: Which Framework Should You Learn?
An honest Django vs Flask comparison for 2026 โ which Python framework to learn first, when each excels, and why FastAPI has changed the equation.
FastAPI Tutorial: Building Your First REST API in 30 Minutes
A hands-on FastAPI tutorial for beginners: build a fully functional REST API in 30 minutes with CRUD endpoints, request validation, and automatic docs.
Jupyter Notebook Guide: The Data Scientist's Favorite Tool
A complete Jupyter Notebook guide for 2026: installation, essential shortcuts, best practices, and how data scientists use Jupyter for exploration, analysis, and sharing.