Django

Lesson 03

QuerySets and lazy evaluation

Compose QuerySets before evaluation and load relationships intentionally to avoid hidden N+1 queries.

Good Code

reviews/selectors.py
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():
    return [
        {
            "title": review.title,
            "author": review.author.username,
            "comment_count": len(review.comments.all()),
        }
        for review in recent_reviews()
    ]

Bad Code

reviews/selectors.py
def review_cards():
    rows = []
    reviews = Review.objects.filter(status="published").order_by("-published_at")[:20]

    for review in reviews:
        rows.append(
            {
                "title": review.title,
                "author": review.author.username,
                "comment_count": review.comments.count(),
            }
        )

    return rows

Review Notes

What to review

Good Code

The good version composes the query, selects the related author, prefetches comments, and evaluates at the edge where cards are built.

Bad Code

The bad version looks small but can trigger extra queries for every review. The performance cost is hidden inside attribute access.

Takeaways

  • QuerySets are lazy; review where they are built, sliced, and finally evaluated.