awesome-everything RU
↑ Back to the climb

APIs

Why GraphQL gets N+1

Crux GraphQL''''s independent-resolver model hands the client flexibility and hands the server an accidental query storm — one HTTP request turns into hundreds of DB calls.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at junior altitude — the surface
◷ 10 min

Bea asks for posts { title, author { name } } — one HTTP request for 50 posts. Sven watches the database log light up: 51 queries. The schema is correct. No bug was introduced. This is GraphQL working exactly as designed, and it is costing 600 ms instead of 80 ms.

How GraphQL executes a query

GraphQL resolves a document by walking it depth-first and breadth-first at each level. For every field the client selected, the server calls the resolver registered for that Type.field pair. Resolvers receive (parent, args, context, info) and return a value or a Promise. They are deliberately isolated: each resolver does not see its siblings, does not know how the parent fetched its data, and cannot inspect what other resolvers will run next.

This isolation is what makes GraphQL composable — you can add a field to a type without touching any other resolver. But it is also the direct cause of N+1.

Why the isolation causes N+1

Consider the query posts { title, author { name } }. The execution engine:

  1. Calls Query.posts → runs SELECT * FROM posts LIMIT 50 → returns 50 post objects.
  2. For each of the 50 posts, calls Post.author(parent=post, ...) → runs SELECT * FROM users WHERE id = post.author_id → 50 separate queries.

Total: 51 database round-trips for one HTTP request.

A REST controller writing the same endpoint would naturally write SELECT posts JOIN users ON users.id = posts.author_id — one query. The developer sees the whole response shape in one place and can optimize the SQL. In GraphQL, the Post.author resolver sees only one post at a time. There is no global planning point.

The waiter metaphor

A restaurant where each table orders items one at a time. A table of six sends the waiter back six times — once per item. The kitchen is fast, but the waiter spends the shift walking. The kitchen’s speed does not matter when the walk is the cost.

DataLoader is the manager who says: hold tickets until I clap, then bring them all at once. The clap is one event-loop tick. This lesson explains the problem; the next two explain the fix.

Without DataLoaderWith DataLoader
Query.posts → 1 SQLQuery.posts → 1 SQL
Post.author(post1) → 1 SQLPost.author × 50 → queue 50 IDs
Post.author(post2) → 1 SQLDataLoader fires 1 SQL for all 50
… (50 rows)Total: 2 SQL
Total: 51 SQL
GraphQL N+1 at a glance
Naive trips for 50 posts × 1 author
51 (1 + 50)
With DataLoader
2 (1 + 1)
Reported DataLoader speedup
64–84% (relational)
DataLoader scope
per-request, not global
Quiz

What does 'N+1' mean in the GraphQL context?

Quiz

Why does GraphQL get N+1 problems while REST often does not?

Order the steps

Put the steps of a naive GraphQL resolution in order:

  1. 1 Client sends one GraphQL query asking for posts and their authors
  2. 2 Server resolves the top-level field 'posts' and gets back 50 posts
  3. 3 For each post, the Post.author resolver fires — 50 independent resolver calls
  4. 4 Each resolver runs SELECT * FROM users WHERE id = post.author_id
  5. 5 Database executes 50 separate queries, one per post
  6. 6 GraphQL assembles the final response and sends it to the client
Recall before you leave
  1. 01
    Why does GraphQL get N+1 while a REST controller on the same data usually does not?
  2. 02
    In one sentence: what is the fix for GraphQL N+1?
Recap

GraphQL execution is field-by-field and resolver-by-resolver. When a list of N items each require a nested fetch, the server fires 1+N database queries by default — not because of a bug, but because resolver isolation is how GraphQL stays composable. A page with 50 posts and their authors costs 51 round-trips without intervention. DataLoader, covered in the next lesson, collapses those 50 per-author trips into one batch, reducing the total from 51 to 2.

Connected lessons
appears again in178
Continue the climb ↑DataLoader mechanics: tick-boundary batching
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.