Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How to spot it in the actual endpoint

  1. 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.
  2. 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.
  3. 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.
  4. Inspect the queryset the endpoint really uses. In Django REST framework, that may be the queryset returned by get_queryset() or the view’s queryset attribute. 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.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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

  1. 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.
  2. 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.
  3. 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 covers explain() in its database optimization documentation.
  4. 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.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Common mistakes to avoid

  • Choosing a method that does not fit the relation. Use select_related() for single-valued relations and prefetch_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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.