The N+1 Query Problem
One query for a list, then one more for every item — a silent performance killer in ORMs.
The N+1 problem happens when code loads a list of N records with one query, then runs another query for each record to load related data. Fifty orders become fifty-one database round trips.
It is easy to create with ORMs such as Entity Framework or Hibernate, because lazy-loaded navigation properties look like normal property access.
Diagram
Key concepts
- Lazy loading
- Related data is fetched the first time it is accessed — convenient, and the main cause of N+1.
- Eager loading
- Related data is fetched up front with a join or a second batched query (
Include,JOIN FETCH). - Batching
- Collect all IDs and run one
WHERE id IN (…)query. GraphQL DataLoader does this automatically. - Projection
- Select only the fields needed into a DTO, letting the database do the join.
Example
Entity Framework Core: N+1 and the fix
// N+1: one query for orders, then one per order for its customer
var orders = await db.Orders.ToListAsync();
foreach (var o in orders)
Console.WriteLine(o.Customer.Name); // lazy load = extra query each time
// Fix: load the related data in the same query
var orders = await db.Orders
.Include(o => o.Customer)
.ToListAsync();
// Or project only what you need
var rows = await db.Orders
.Select(o => new { o.Id, CustomerName = o.Customer.Name })
.ToListAsync();Trade-offs
Benefits
- Fixing it often turns seconds into milliseconds
- Reduces database connections and load
Costs
- Over-eager loading can pull huge object graphs
- Large joins can duplicate parent rows (cartesian explosion) — consider split queries
Use it when
- Any list page that shows related data
- GraphQL resolvers that load child entities per parent
Common mistakes
- Hidden N+1 inside serialisation, when the JSON serialiser touches lazy properties
- Tests with 3 rows that never reveal the problem
- Loops calling a repository or remote API per item
Interview questions
Q1How do you detect N+1 queries?
Log SQL in development, use an APM or profiler that counts queries per request, or add a test that asserts the query count for a list endpoint.
Q2How does GraphQL make N+1 more likely, and what fixes it?
Each field resolver runs per parent object, so resolving
author for 100 posts means 100 lookups. A DataLoader batches them into one query per request.Q3Is a single huge JOIN always the best fix?
No. Joining several one-to-many collections multiplies rows. Two or three batched queries (split queries) can be faster.
Related lessons
Data & Storage
Indexing & Query Performance
How indexes turn full table scans into fast lookups — and the other habits that keep queries quick.
APIs & SecurityImproving API Performance
Pagination, caching, compression, async processing and connection pooling.
APIs & SecurityAPI Architecture Styles
REST, GraphQL, gRPC, WebSockets, webhooks and SOAP — what each is for.