Introduction
You've optimized your database queries, you're using async where you can, and your code looks clean — yet your app still feels sluggish under load. The culprit is almost never where you expect. In this post, I'll walk through 4 tiny, easy-to-miss mistakes that quietly kill your app's performance, and exactly how to fix them with Python examples.
The Problem
Most performance bottlenecks don't come from big architectural flaws. They hide in small habits: blocking calls in async functions, unnecessary object creation in tight loops, unclosed resources, and ignoring caching for repeated computations. These compound silently and become critical at scale.
The Solution
Let's go through each mistake with a concrete Python example and the fix.
Mistake 1: Blocking I/O inside async functions
Using time.sleep() or synchronous requests inside an async function blocks the entire event loop.
Mistake 2: Creating objects inside tight loops
Reusing objects instead of recreating them on every iteration saves significant memory and CPU.
Mistake 3: Not closing resources properly
Unclosed file handles, DB connections, or HTTP sessions accumulate and exhaust system resources.
Mistake 4: Recomputing the same result repeatedly
If a function returns the same result for the same inputs, cache it — don't recompute.
import asyncio
import httpx
from functools import lru_cache
from contextlib import asynccontextmanager
# ❌ MISTAKE 1: blocking call inside async — kills event loop
async def bad_fetch():
import time
time.sleep(2) # blocks everything!
...
# ✅ FIX 1: use await with async-compatible sleep / HTTP client
async def good_fetch(url: str):
async with httpx.AsyncClient() as client:
response = await client.get(url)
return response.json()
# ❌ MISTAKE 2: object creation inside a tight loop
def bad_loop(data: list):
results = []
for item in data:
prefix = "user_" # recreated 10,000x
results.append(prefix + str(item))
return results
# ✅ FIX 2: hoist static objects outside the loop
def good_loop(data: list):
prefix = "user_" # created once
return [prefix + str(item) for item in data]
# ❌ MISTAKE 3: resource leak — session never closed
def bad_session():
import requests
session = requests.Session()
r = session.get("https://api.example.com/data")
return r.json()
# session is never closed — leak!
# ✅ FIX 3: always use context managers
def good_session():
import requests
with requests.Session() as session:
r = session.get("https://api.example.com/data")
return r.json()
# ❌ MISTAKE 4: recomputing the same expensive result
def bad_compute(n: int):
total = 0
for i in range(n): # called 1000x with same n = same result
total += i
return total
# ✅ FIX 4: cache with lru_cache
@lru_cache(maxsize=128)
def good_compute(n: int):
total = 0
for i in range(n):
total += i
return total
# Demo
if __name__ == "__main__":
# Test loop optimization
data = list(range(10_000))
print(good_loop(data[:5])) # ['user_0', 'user_1', ...]
# Test caching
import time
start = time.perf_counter()
for _ in range(1000):
good_compute(100_000)
end = time.perf_counter()
print(f"Cached compute x1000: {end - start:.4f}s")
# Test async fetch
async def main():
result = await good_fetch("https://jsonplaceholder.typicode.com/todos/1")
print("Fetched:", result)
asyncio.run(main())
Result
By applying these 4 fixes you'll typically see:
- 2–10x faster async throughput by eliminating event loop blocking
- Lower memory usage in loops processing large datasets
- No more connection pool exhaustion thanks to proper resource cleanup
-
Near-instant repeated computation with
lru_cache
Small changes, massive impact at scale.
Want more Python performance tips and automation projects? Check out codes-me.com — new content every week. Drop a comment with the mistake you've hit the most in production 👇
Enjoyed this article? Feel free to check out my profile for more Python tutorials, automation tips, and open-source projects.
Top comments (0)