What is the difference between select_related and prefetch_related in Django?
Both methods solve the N+1 query problem in Django, but they work differently and apply to different relationship types. Choosing the wrong one still causes N+1 or unexpected query counts.
**`select_related`** performs a SQL JOIN. It fetches the related objects in a single query by joining the related table. It only works for **ForeignKey** and **OneToOne** relationships (relations that follow a single join path). It is depth-unlimited but each level adds a JOIN.
```python # Without select_related: 1 + N queries orders = Order.objects.all() for order in orders: # N queries for order.customer print(order.customer.name)
# With select_related: 1 query with JOIN orders = Order.objects.select_related('customer').all() ```
Generated SQL: `SELECT order.*, customer.* FROM order INNER JOIN customer ON order.customer_id = customer.id`
`select_related('customer__address')` traverses multiple levels with chained JOINs.
**`prefetch_related`** executes separate queries — one per relationship — and performs the join in Python. It handles **ManyToMany**, **reverse ForeignKey** (accessing the many side of a FK relation), and GenericRelatedObjectManager. It can also handle ForeignKey/OneToOne, but `select_related` is more efficient for those.
```python # Fetches authors, then a separate query for all books of those authors authors = Author.objects.prefetch_related('book_set').all() for author in authors: for book in author.book_set.all(): # uses prefetch cache, no extra query print(book.title) ```
Generated SQL: Query 1: `SELECT * FROM author`; Query 2: `SELECT * FROM book WHERE author_id IN (1, 2, 3, ...)`
**`Prefetch` object with custom querysets:** `prefetch_related(Prefetch('book_set', queryset=Book.objects.filter(published=True)))` — lets you apply filters, ordering, or annotations to the prefetched queryset. Critical when you need a subset of related objects.
**Common mistakes:** - Using `select_related` on a ManyToMany — it doesn't work; use `prefetch_related` - Calling `.filter()` on a prefetched relation in a loop — this invalidates the cache and causes N+1 again. Use `Prefetch()` with the filter applied in advance - Not using either — relying on Django's lazy evaluation in templates where the N+1 is invisible
Use `django-debug-toolbar` or `django.db.connection.queries` to verify query count.
| Aspect | select_related | prefetch_related |
|---|---|---|
| SQL mechanism | JOIN (single query) | Separate queries + Python join |
| Applicable relations | ForeignKey, OneToOne | ManyToMany, reverse FK, all types |
| Query count | 1 (or 1 per level) | 1 + N relations (constant) |
| Data duplication risk | High if many rows join | None (separate result sets) |
| Custom queryset support | No | Yes (Prefetch object) |
| Loop filter safe | N/A | No — invalidates cache |
Correctly explains that select_related uses JOINs for FK/OneToOne and prefetch_related uses separate queries for M2M/reverse FK, with the N+1 problem as motivation.
Covers SQL mechanics, applicable relation types, the Prefetch() object for custom querysets, the loop filter invalidation pitfall, and knows how to verify query counts.
Reading the answer is step one. Explaining it unprompted — under interview pressure — is what actually matters. Get AI-graded feedback on your answer with follow-up probes on your weak points.
Get Graded — Free Assessment