What is the N+1 problem in GraphQL and how does DataLoader solve it?
Understand the GraphQL N+1 query problem and how DataLoader batches and caches per-item lookups into one query to cut database round trips.
Expected Interview Answer
The N+1 problem is when resolving a list of N items triggers 1 query for the list plus N additional queries — one per item — to fetch a related field, producing N+1 total round trips. DataLoader solves it by batching those N per-item lookups into a single query and caching results within a request.
Because GraphQL resolves fields independently, a nested field resolver runs once for every parent in a list, so fetching each post's author yields one database call per post. DataLoader wraps a batch function and, using the JavaScript event loop, collects all the individual .load(key) calls made during a single tick into one .loadMany-style call, then dispatches them together. It also memoizes by key so repeated loads of the same id return the cached value instead of hitting the data source again.
- Collapses N per-item queries into a single batched query
- Per-request caching avoids fetching the same key twice
- Dramatically reduces database and network round trips
- Keeps resolvers simple — batching logic lives in one place
- Works with any backend: SQL, REST, or microservices
AI Mentor Explanation
Imagine a coach who, for each of eleven batters, walks to the records room separately to fetch that one player's career file — eleven trips for eleven players, plus the first trip to get the team list. A smart assistant instead writes down all eleven names, makes one trip, and returns with every file at once. DataLoader is that assistant: it gathers all the per-player lookups fired in a moment and satisfies them in a single visit to the records room.
Step-by-Step Explanation
Step 1
Spot the list field
A query returns N parents, e.g. a list of posts, each needing a related field like author.
Step 2
See the fan-out
The author resolver runs once per post, issuing N separate lookups plus the initial list query.
Step 3
Create a loader
Define a DataLoader with a batch function that takes an array of keys and returns values in the same order.
Step 4
Call load per item
Each resolver calls loader.load(authorId) instead of querying directly.
Step 5
Batch on the tick
DataLoader collects all load calls in one event-loop tick and invokes the batch function once.
Step 6
Cache within request
Repeated keys resolve from the loader's per-request cache; create loaders fresh per request in context.
What Interviewer Expects
- Clear definition of why N+1 arises from per-field resolution
- Understanding that DataLoader batches via the event loop tick
- Knowing the batch function must return results in key order
- Awareness of per-request caching and creating loaders per request
- Recognizing it applies to any data source, not just SQL
Common Mistakes
- Sharing a single DataLoader across requests, leaking stale cache
- Returning batch results out of order or wrong length
- Thinking joins alone always solve it across nested/microservice data
- Forgetting the +1 initial list query in the count
- Assuming DataLoader batches across separate ticks
Best Answer (HR Friendly)
“The N+1 problem is when loading a list of things makes the server run one extra database query for every single item, which gets slow fast. DataLoader fixes it by grouping all those little lookups into one combined query and remembering results so the same thing is never fetched twice in a request.”
Code Example
import DataLoader from 'dataloader'
// Create a fresh loader per request inside context
function createContext(db) {
const authorLoader = new DataLoader(async (ids) => {
const authors = await db.authors.findByIds(ids)
// Must return values in the SAME order as ids
const map = new Map(authors.map((a) => [a.id, a]))
return ids.map((id) => map.get(id) || null)
})
return { db, authorLoader }
}
const resolvers = {
Post: {
// Runs once per post, but loads are batched into one query
author: (post, _args, ctx) => ctx.authorLoader.load(post.authorId),
},
}Follow-up Questions
- Why must a DataLoader be created per request rather than shared globally?
- What contract must the batch function honor regarding order and length?
- How does DataLoader use the event loop to decide when to batch?
- When would a SQL join be a better solution than DataLoader?
- How does DataLoader's caching differ from a full response cache?
MCQ Practice
1. In the N+1 problem, what does the '1' refer to?
The 1 is the original query that returns the list of N items; the N are the per-item follow-up queries for a related field.
2. How does DataLoader decide which load() calls to batch together?
DataLoader collects every .load() call scheduled in a single tick of the event loop and dispatches the batch function once for them.
3. Why should you create a DataLoader per request?
The per-request cache would otherwise persist stale or cross-user data; a fresh loader per request scopes caching correctly.
Flash Cards
What is the N+1 problem? — 1 query for a list of N items plus N per-item queries for a related field = N+1 round trips.
How does DataLoader batch? — It collects all .load(key) calls in one event-loop tick and runs the batch function once.
Batch function contract? — Given an array of keys, return an array of values in the same order and length.
Why per-request loaders? — The per-request cache must not leak stale or cross-user data between requests.
Does DataLoader cache? — Yes — it memoizes by key within a request so duplicate loads hit cache, not the source.