Good Code
The good version composes the query, selects the related author, prefetches comments, and evaluates at the edge where cards are built.
Lesson 03
Compose QuerySets before evaluation and load relationships intentionally to avoid hidden N+1 queries.
# Build the QuerySet before evaluating it.
def recent_reviews():
return (
Review.objects.filter(status=Review.Status.PUBLISHED)
.select_related("author")
.prefetch_related("comments")
.order_by("-published_at")[:20]
)
def review_cards():
# Related rows are already loaded before this loop runs.
return [
{
"title": review.title,
"author": review.author.username,
"comment_count": len(review.comments.all()),
}
for review in recent_reviews()
]def review_cards():
rows = []
reviews = Review.objects.filter(status="published").order_by("-published_at")[:20]
for review in reviews:
rows.append(
{
"title": review.title,
# Relationship access in the loop can create N+1 queries.
"author": review.author.username,
"comment_count": review.comments.count(),
}
)
return rowsThe good version composes the query, selects the related author, prefetches comments, and evaluates at the edge where cards are built.
The bad version looks small but can trigger extra queries for every review. The performance cost is hidden inside attribute access.