Laziness and the N+1 Problem

Querysets are lazy. Building one runs nothing. The database is touched when you iterate it, index it, or ask for its length.

That is why a query can be assembled across several lines of a view. It is also why a query written inside a template loop can quietly run hundreds of times.

The N+1 problem. You fetch fifty posts: one query. The template prints each post's author name: fifty more queries, one per post, because the author was never fetched.

Fifty-one queries where two would do. The page still works, so nothing tells you. It just gets slower as the data grows, which is exactly when it is hardest to fix.

Fetching related data

Naive

# 1 + N queries
posts = Post.objects.all()

# template:
#   {{ post.author.username }}
#   runs a query per post

Fixed

# 1 query, joined
posts = Post.objects.select_related(
    "author"
)

# 2 queries, matched up
posts = Post.objects.prefetch_related(
    "tags"
)
Compare → select_related for ForeignKey and OneToOne, because a join can do it. prefetch_related for ManyToMany and reverse relations, because a join cannot: it runs a second query and matches the results in Python.

✦ Install the Debug Toolbar

Django Debug Toolbar shows the query count for every page. Put it on a list page you have already built and look at the number. Almost everyone is surprised the first time, and it turns performance from guesswork into something you can read off the screen.

Mark this lesson complete

Signed in, your progress follows you to every device.

Next: Practice Test →
← Back