Question presented to candidate: "You have added a GraphQL layer over your Node service. A list of 20 posts is suddenly making 21 database queries. What is happening, and how do you fix it?"
What a strong answer should cover:
- A minimal GraphQL server needs three things: a schema (the types and what fields exist), resolvers (functions that produce each field's value), and something to execute a query against them β
graphql-jsdirectly, or a server layer like Apollo Server /graphql-yogaon top. - A resolver runs independently, per field, per object in the result set. A naive
Post.authorresolver that does its own database lookup runs once for every post returned, not once for the whole list. - π The N+1 problem: 1 query to fetch N posts, then N more queries β one per post β to fetch each post's author. Even when several posts share the same author, a naive resolver does not know that and repeats the lookup.
- DataLoader is the standard fix: it does not change the resolver's call site (
authorLoader.load(id)still looks like one call per post), but it collects every.load()call made within the same event-loop tick, deduplicates the ids, and issues one batched call for all of them. - The batching is per-request, not global β a fresh
DataLoaderinstance per incoming GraphQL request (created in request context) is the standard pattern; a shared long-lived loader risks serving stale or cross-request-leaked cached values. - This is a general resolver-composition problem, not specific to any one GraphQL library β REST code that loops and queries per item has the exact same shape of bug; GraphQL just makes it easy to trigger by accident because nested field selection is the whole point of the query language.
Clarifying questions expected:
- "Is the N+1 happening on every field, or specifically on associations/relations resolved per item?" β narrows it to the resolver doing the per-item lookup.
- "Is a DataLoader instance shared across requests, or created fresh per request?" β a shared instance is a correctness risk (stale/cross-user data), not just a performance one.
Code / implementation expected: Yes β showing the naive resolver, then the DataLoader-batched version, with the call counts, is the clearest way to make the fix concrete.