1. N+1 from a lazily-evaluated foreign key
Symptom
A list view that was instant with 20 rows takes 8 seconds at 500. Django Debug Toolbar shows 501 queries.
The pattern
# views.py
def order_list(request):
orders = Order.objects.all()[:500]
return render(request, "orders.html", {"orders": orders})
# orders.html
# {% for order in orders %}{{ order.customer.name }}{% endfor %}Why it bites
`order.customer` is a lazy descriptor: the first access per object issues its own SELECT. The template gives no syntactic hint that a database call is happening, so the cost is invisible in review and scales linearly with row count.
The fix
# One JOIN instead of 500 round trips.
orders = Order.objects.select_related("customer").all()[:500]
# For many-to-many or reverse FKs, prefetch_related does it in 2 queries:
orders = (
Order.objects
.select_related("customer")
.prefetch_related("line_items__product")[:500]
)