Thodoris Kouleris
Software Engineer
Killing the N+1 Query Problem
It’s a classic backend scenario: your /orders endpoint fetches 50 orders for a dashboard page. CPU usage is low, memory looks great, database metrics are stable, and nothing is firing off alerts in PagerDuty.
Yet, your P95 latency sits at a sluggish 2.4 seconds (95% of server or app requests finish faster than 2.4s).
Why? You’ve hit the N+1 Query Problem.
The Initial Fetch (1 Query): The Order Service queries the Orders DB to get a list of orders.
SELECT * FROM orders LIMIT 50;
The Hidden Loop (N Queries): To display customer details for each order, your code iterates over those 50 orders and makes an individual database lookup for every single customer.
-- Executed 50 times!
SELECT * FROM customers WHERE id = 123;
SELECT * FROM customers WHERE id = 124;
...
Even if each query takes only 10ms, 50 separate round-trips over the network add up to 500ms+ of pure network latency overhead, pushing your overall endpoint response time into multi-second territory.
How to Fix It
Here are the standard, battle-tested ways to resolve the N+1 query problem depending on your architecture:
1. Batching
If Orders DB and Customers DB are separate databases (or microservice boundaries), you cannot do a database-level join. Instead, batch the customer IDs into a single query.
Step 1: Fetch the 50 orders.
Step 2: Extract the unique customer_id values array ([123, 124, ...]).
Step 3: Fetch all required customers in a single query:
SELECT * FROM customers WHERE id IN (123, 124, 125, ...);
Step 4: Map the customer objects back to the orders in application memory.
2. SQL JOINs
If orders and customers live within the same relational database schema, join them directly in a single SQL execution:
SELECT
o.id AS order_id,
o.total,
c.id AS customer_id,
c.name,
c.email
FROM orders o
JOIN customers c ON o.customer_id = c.id
LIMIT 50;
Result: Drops total database calls from 51 down to 1.
3. ORM Eager Loading
Eager loading is an optimization technique used in Object-Relational Mapping (ORM) frameworks to improve database performance by retrieving related data as part of the initial query instead of loading it on demand. This approach helps prevent the "N+1 query" problem, where additional database queries are executed for each record when accessing related entities inside loops. By loading all required relationships upfront, eager loading reduces the total number of database queries, lowers latency, and improves the efficiency and scalability of applications, especially when working with collections of related data.
---
A healthy database doesn't mean efficient application logic. Whenever you notice high latency on paginated or list endpoints, inspect your query logs—you're likely paying the N+1 tax!