Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
A Django API list can issue one query to fetch its objects, then another query for each object as the serializer reads related data. That N+1 pattern can make database activity—and latency—grow with the number of results. Find the repeated access in the endpoint’s full serialization path, then use select_related() for single-valued relationships or prefetch_related() for collections. Verify the response and database work after the change; fewer SQL statements alone do not prove the endpoint is faster.
What the N+1 query problem looks like
An N+1 pattern starts with a query that retrieves a collection of objects. Code then accesses a related object separately for each item, triggering repeated queries. If a list contains N objects, the database may see the initial query plus roughly N related-object queries.
In an API, the access is not always obvious in the view. It can happen in a serializer field, a serializer method, a model property, or another part of the code used to render each result. Django recommends identifying repeated database work and loading related data in a way that fits how the endpoint uses it. Django’s database optimization guidance discusses these practices.
How to spot it in the actual endpoint
- Reproduce a representative request. Use the affected list endpoint with a realistic result size, permissions, and the serializer used in production. A one-object response may not make per-item queries apparent.
- Inspect the SQL and query counts. In development, django-debug-toolbar can help show queries for a request. Django also points to
django.db.connection.queries,QuerySet.explain(), and direct database monitoring as investigation tools. See Django’s optimization documentation. - Look for repeated query shapes. A likely N+1 is the same kind of query repeated with a different foreign-key value for each serialized object. Trace which serializer field, method, model property, or view access causes it.
- Inspect the queryset the endpoint really uses. In Django REST framework, that may be the queryset returned by
get_queryset()or the view’squerysetattribute. DRF specifically warns that serializers accessing related objects individually can cause N+1 queries and recommends optimizing that queryset. See DRF’s generic views documentation.
Choose the loading method by relationship shape
The two main ORM methods use different strategies. Match the method to the relationship and the data the endpoint actually reads.
#1 Best Overall
| Relationship needed | Method | How it retrieves data |
|---|---|---|
| Single-valued relation, such as a foreign key or one-to-one field | select_related() |
Uses an SQL join to include related fields in the query. |
| Collection, such as a many-to-many or reverse relationship | prefetch_related() |
Runs separate lookup queries and associates the results in Python. |
Django documents these distinct strategies and their relationship limits in its QuerySet API reference. For example, a serializer that reads an order’s customer and line items may need select_related("customer") and prefetch_related("line_items") on the endpoint queryset. Those field names are illustrative: use the actual model relationships and confirm that they match the serializer’s access path.
Use select_related() for one related object
Use it when each returned object points to a single related row, such as a foreign key or one-to-one relation. The database joins that related data into the main query. It is not the right method for a many-to-many collection.
Rank #2
Use prefetch_related() for collections
Use it when each object may have a set of related rows, including many-to-many and reverse relationships. Django fetches the related rows separately, then connects them to the primary objects in Python. If the endpoint needs a filtered or customized subset, investigate the Prefetch API and validate the resulting SQL and response.
Combine methods when the response needs both shapes
An endpoint may need a single-valued relation as well as a collection. In that case, combine the appropriate methods on the queryset, guided by the fields the view and serializer actually access.
Apply the fix to the endpoint queryset
For a DRF list view, add relationship loading to the queryset used by that view rather than assuming an optimization elsewhere will apply. A basic pattern might look like this:
def get_queryset(self):
return Order.objects.select_related("customer").prefetch_related("line_items")
This example assumes the serializer reads a single customer and a line-item collection. Substitute real model and relation names, and avoid loading relations the response does not use. DRF describes queryset optimization in get_queryset() or the class-level queryset attribute in its generic views guidance.
Verify correctness, query behavior, and cost
- Repeat the same endpoint request. Compare the SQL and repeated query patterns before and after the change. Confirm that the per-object queries have been reduced as expected.
- Check the response as well as the query count. Make sure the returned data and permission-dependent behavior remain correct. A queryset change should not silently alter what the serializer can return.
- Inspect a slow query’s plan when needed.
QuerySet.explain()can show database execution details. Removing repeated queries does not establish that the remaining query uses an optimal plan or that indexes are adequate; investigate those separately. Django coversexplain()in its database optimization documentation. - Measure memory and response size. Prefetching fully loads the primary queryset results and prefetched related objects into memory. For large result sets, pagination or a different retrieval pattern may be more appropriate. Django notes this consideration in its QuerySet API reference.
Common mistakes to avoid
- Choosing a method that does not fit the relation. Use
select_related()for single-valued relations andprefetch_related()for collections. - Inspecting only the view body. Serializer fields and methods can trigger the hidden per-item access, so follow the full rendering path.
- Assuming every access uses the prefetched result. Extra filtering or a different way of accessing a relation may issue new queries. Check the SQL produced by the actual endpoint.
- Optimizing every caller or manager by default. Apply loading where the endpoint needs it and measure the effect; different callers may use different querysets.
- Treating query count as the only performance measure. Joins, execution plans, result volume, and memory use also affect endpoint performance.
Documentation version note
The Django optimization guidance linked above is for Django 6.1, while the cited QuerySet API reference is for Django 4.0. Check the documentation for your installed Django release before relying on version-specific API details.
Recommended Free Tools
Quick Recap
Best Value
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

