What Is the N+1 Query Problem?
Imagine an application that displays a list of users and the number of orders they have placed, or an overview of their orders. The application executes one database query to retrieve the list of users. However, it then retrieves each user's orders separately.
If we have 100 users, the result could be:
- 1 query to retrieve all users.
- 100 additional queries to retrieve each user's orders.
That adds up to 101 database queries instead of a small number of optimized queries that could retrieve the required data more efficiently.
The problem is called N+1 because the application executes one initial query followed by N additional queries, typically one for each record in the result set.
What happens when the application has 1,000 users? Instead of one query, it might execute 1,001. Each individual query may be fast, but their combined execution increases loading times and puts unnecessary pressure on the database and communication between the application and the database server.
How Does the N+1 Problem Occur in Django?
Django uses an ORM (Object-Relational Mapper), which allows developers to work with database records through Python objects rather than writing SQL queries for every operation.
The ORM simplifies development, but it is not always obvious how many database queries are actually being executed.
Let's look at a simplified example. Assume we have a User model and an Order model, where each order belongs to a specific user and the relationship is configured with related_name="orders".
users = User.objects.all()
for user in users:
orders = user.orders.all()
print(user.username, list(orders))
The code looks straightforward. First, we retrieve the users, then we fetch each user's orders.
The catch is that user.orders.all() may execute a separate database query for every user. If the list contains 100 users, the application may execute 101 queries.
This behavior can easily go unnoticed in templates, serializers, or nested loops. Developers work with objects and their relationships, while the database performs repeated operations behind the scenes.
How to Fix It: select_related() and prefetch_related()
Django provides two important methods for optimizing the retrieval of related objects: select_related() and prefetch_related().
Both help reduce unnecessary database queries, but they work differently. The right choice depends on the type of relationship between the models.
1. select_related(): When Records Are Connected Through a Foreign Key
The select_related() method is primarily used with ForeignKey and OneToOneField relationships. Django uses an SQL JOIN to retrieve the related data within a single query.
Consider the reverse scenario: we want to display a list of orders along with the name of the user who placed each order.
The unoptimized code might look like this:
orders = Order.objects.all()
for order in orders:
print(order.user.username)
If we have 100 orders, Django may execute one query to retrieve the orders and another 100 queries to retrieve the corresponding users.
The optimized version looks like this:
orders = Order.objects.select_related("user")
for order in orders:
print(order.user.username)
Django retrieves the orders and their related users together. Instead of fetching the user separately for every order, a single SQL query may be enough.
When should you use select_related()?
Use it when you need to retrieve a related object through a relationship where each record has at most one related object. Examples include retrieving the user associated with an order or a profile belonging to a single user.
2. prefetch_related(): When You Need Multiple Related Records
But what if we want to display users and all their orders? A user can have no orders, one order, or dozens of orders. This is where prefetch_related() comes in.
users = User.objects.prefetch_related("orders")
for user in users:
print(user.username)
for order in user.orders.all():
print(order.id)
Django first retrieves the users, then executes a separate query to fetch the orders associated with them. It subsequently matches those orders to the appropriate users in the application.
In a typical example, this means two database queries instead of 101: one for the users and another for their orders.
prefetch_related() is particularly useful for ManyToManyField relationships and reverse ForeignKey relationships, where one object can be associated with multiple other objects.
Unlike select_related(), prefetch_related() uses separate queries and combines the results in Python. This also makes it suitable for situations where a standard SQL JOIN would result in repeated data in the result set.
It is important to remember that fewer queries are not automatically better in every situation. The goal is to reduce unnecessary work and choose an approach that fits the data and the specific use case.

Lazy Loading: Why Is a Query Executed Later?
One reason the N+1 problem is easy to overlook is lazy loading, which means that data is retrieved only when it is actually needed.
Django QuerySets are generally evaluated only when the application needs their results. This happens, for example, when iterating over a QuerySet, converting it into a list, or performing certain other operations.
Similarly, related objects accessed through foreign keys are often retrieved only when the code accesses them. If an application repeatedly accesses relationships inside a loop without loading them in advance, it can unintentionally generate additional queries.
This is why the problem may not be visible on the line that retrieves the list of users. It may only appear later, for example, in a template that displays a user's name, orders, or other related information.
Lazy loading is not inherently a problem. It helps ensure that only the data an application needs is retrieved. The issue arises when deferred loading happens repeatedly, generating unnecessary database operations.
How to Detect the N+1 Query Problem
The first step is to determine how many database queries the application actually executes. Looking at the Python code alone is often not enough.
There are several ways to identify the problem.
Django Debug Toolbar is a useful tool during local development. It can display the SQL queries executed when a page loads, including their number and execution time. If the same or very similar query appears dozens or hundreds of times, it may indicate a problem.
SQL query logging can help with more detailed diagnostics. Developers can monitor which queries are executed, how often they run, and how long they take. In production, logging should be configured carefully to avoid creating unnecessary overhead or exposing sensitive data.
Query-count tests help detect performance regressions before deployment. Django provides assertNumQueries(), for example, which can be used in tests to verify that a particular operation does not execute an unexpectedly large number of database queries.
Profiling slow pages and API endpoints helps identify where optimization will have the greatest impact. For APIs, it is also worth checking serializers, as they may access related records while processing each object, triggering additional queries.
Not every repeated query automatically indicates an N+1 problem, and not every slow endpoint has one. The important thing is to consider the number of queries in relation to the amount of data being processed and the time those queries actually take.
How to Prevent the N+1 Problem
The best optimization is one you do not have to implement after users start complaining about a slow application. A few development practices can significantly reduce the risk of N+1 queries.
- Think about database relationships when designing a feature. If you know a page will display orders alongside user names, plan how to retrieve the required related data in advance.
- Use select_related() and prefetch_related() where appropriate. Do not assume the ORM will automatically choose the most efficient way to retrieve all the data you need. At the same time, avoid adding these methods indiscriminately. Retrieving large amounts of unnecessary data can be just as inefficient.
- Check templates and serializers. The N+1 problem does not have to originate directly in a view. It can also occur in code that processes individual objects while rendering a page or generating an API response.
- Test with realistic amounts of data. With five users, the difference between six queries and two may be almost unnoticeable. With hundreds or thousands of records, the impact can be significant. It is also worth monitoring not just the number of queries, but their execution time and the amount of data retrieved.
- Monitor performance continuously. Query-count checks in tests, profiling, and regular analysis of slow endpoints help identify problems before they become noticeable under heavier workloads.
Conclusion: Fewer Queries Can Mean a Faster Application
The N+1 Query Problem is a good example of how a seemingly minor detail in the code can gradually turn into a performance bottleneck. An application may function correctly while doing unnecessary work as the amount of data grows.
In Django, understanding when data is retrieved and correctly using select_related() or prefetch_related() can often make a significant difference. Instead of executing one initial query followed by dozens or hundreds of additional ones, the application can retrieve the required data through a few more efficient operations.
Database optimization is not just about making each query fast. It is also about reducing the number of queries the application needs to execute in the first place.