Personal Catalogue: Django & PostgreSQL — Chapter 4, Exercise 2 ==================================================== TASK Explain, step by step, why removing prefetch_related('tags') from the list view's own queryset would produce exactly 26 real database queries to render a page of 25 items with tags — not 25, and not 1. SOLUTION STEP 1: THE INITIAL QUERY Rendering the list page starts with one real query to fetch the page's own 25 Item rows (Django's ORM is lazy, so this query actually runs the first time the queryset is evaluated, typically when the template begins iterating over it). That's query #1. STEP 2: THE TEMPLATE LOOP OVER ITEMS The template's {% for item in items %} loop then processes each of those 25 items in turn. For each one, it hits {% for tag in item.tags.all %} - and item.tags is a ManyToManyField manager, which, without prefetch_related already having populated it, doesn't know which tags belong to this item yet. Evaluating item.tags.all triggers its own fresh database query, scoped to just that one item's own tags. STEP 3: WHY THIS HAPPENS ONCE PER ITEM, NOT ONCE TOTAL Django's ORM has no way to know, at the point the initial 25-item query ran, that the template will later ask for each item's own tags - that information only becomes relevant once the template actually reaches each item.tags.all call, one at a time, inside the loop. Without prefetch_related telling Django up front to fetch all the tags for every item on the page in one batched follow-up query, each of the 25 individual item.tags.all calls has no choice but to issue its own separate query - 25 queries, one per item. STEP 4: THE TOTAL Query #1 (the 25 items) plus 25 separate queries (one per item's own tags.all call) totals 26 real queries to render a single page - not 25 (that would ignore the initial items query), and not 1 (that would ignore that ManyToManyField access is lazy and per-item without prefetching). ANSWER: One query fetches the 25 items themselves; then, because tags.all is evaluated separately for each of those 25 items inside the template loop, and Django's ORM has no way to know in advance that every item's own tags will be needed, each of those 25 evaluations triggers its own separate query - 1 + 25 = 26 total queries for one page render. WHY THIS WORKS AS AN ANSWER ---------------------------- It walks through the exact sequence of query evaluation (lazy queryset, then per-item lazy relationship access inside the template loop) rather than simply asserting "N+1 happens," arriving at the specific requested number through that mechanism rather than by guessing.