GraphQL
Let the client ask for exactly what it needs — and inherit a whole new class of problems.
45 minDifficulty 3/5apiAI-writtenWritten by a model on 16 August 2026 and not yet read by a person. Checked automatically: schema, the pedagogical rules the hand-written material is held to, and every diagram parsed for real.
Before this
Why this exists
A mobile app screen needs a user's name and their five most recent orders' totals — nothing else. A REST API might require three separate requests (`/users/5`, `/orders?user=5`, and each order's details) or one bloated endpoint returning far more than needed. GraphQL lets the CLIENT specify exactly which fields it wants, in one request, and gets back exactly that shape, nothing more.
The mental model
A REST endpoint's response shape is fixed by the SERVER — the client takes what it's given. A GraphQL query's response shape is specified by the CLIENT, field by field, against a schema the server defines — the server describes what's POSSIBLE to ask for, and each request describes exactly what's WANTED this time.
How it works
One query can traverse relationships that would otherwise take several REST requests
A query asking for `user { name, orders { total, items { name } } }` returns the user, their orders, AND each order's items, all nested correctly, in one round trip — the equivalent REST approach requires a request per relationship traversed (`/users/5`, then `/orders?user=5`, then per-order `/items?order=X`), each one a separate round trip.
Resolvers are the actual data-fetching logic behind each field
Every field in a GraphQL schema is backed by a RESOLVER function — a small piece of server code that knows how to fetch that specific piece of data, often from a database or another service. The `orders` field's resolver might run a database query filtered by the parent user's id; GraphQL's job is orchestrating which resolvers to call and in what order, based on what the client actually asked for.
N+1 is GraphQL's most common performance trap
A naive resolver for `orders { user { name } }` across 50 orders calls a separate 'get user by id' resolver 50 TIMES — once per order — even though many orders likely share the same handful of users. This is the exact N+1 problem (see `n-plus-one`), and it's especially easy to introduce in GraphQL because each field's resolver is written independently, unaware of its siblings' needs.
A single query's cost is unpredictable from the URL alone, unlike REST
A REST endpoint's cost is roughly knowable in advance — `GET /users/5` does a bounded amount of work. A GraphQL query's cost depends entirely on how deeply it's nested and how many resolvers it triggers, which the CLIENT controls — a maliciously or accidentally deep, wide query can trigger an enormous amount of server work from what looks like one innocuous request, which is why query depth/complexity limiting is a standard production concern.
The mechanism
A client sends a single query describing exactly which fields it wants, potentially nested across relationships. The GraphQL server parses this against its schema, then executes the corresponding resolver function for each requested field, passing each resolver its parent object so nested fields (like a user's orders) know which parent they belong to. The results are assembled into a single JSON response shaped exactly like the original query.
What people get wrong
- GraphQL is always faster than REST because it avoids over-fetching data.
- GraphQL avoids over-fetching FIELDS, but a naive resolver implementation can massively UNDER-perform via the N+1 problem — fetching exactly the right fields, inefficiently, 50 separate times, can be slower than a REST endpoint that fetches slightly more data in one efficient query. This is why GraphQL performance depends heavily on resolver implementation quality (using techniques like batching/DataLoader) — the query language's flexibility doesn't automatically guarantee efficient execution underneath it.
- A GraphQL API has no need for authorization checks, since the client only gets fields it explicitly asked for.
- Asking for a field explicitly doesn't mean the requester is ALLOWED to see it — each resolver still needs its own authorization check (can THIS caller see THIS field, for THIS specific parent object), which GraphQL doesn't provide automatically. This is a common early mistake: assuming the schema's structure implies access control, when authorization has to be explicitly implemented inside resolvers just as it would in a REST endpoint.
- Because GraphQL uses one endpoint, all queries are equally cheap to serve.
- A single query's actual cost varies enormously based on its depth and breadth, which the CLIENT controls — one endpoint doesn't mean uniform cost, and unbounded query complexity is a genuine denial-of-service risk that requires explicit limiting. This misconception has led to real production incidents where deeply nested, client-constructed queries accidentally (or maliciously) triggered enormous server-side work from what looked like one simple request.
When not to use it
- The API's clients have simple, largely uniform data needs, and the overhead of a GraphQL layer isn't justified.
- A well-designed REST API — GraphQL's flexibility is most valuable when clients have genuinely varied, evolving data needs, which not every API has.
- You need aggressive HTTP-level caching (CDN caching by URL) for public, cacheable content.
- REST's per-resource URLs cache naturally at the HTTP layer; GraphQL's single endpoint and POST-based queries make this kind of caching significantly harder to achieve without additional tooling.
Terms
- Resolver
- — A server-side function responsible for fetching the data for one specific field in a GraphQL schema.
- Schema
- — The definition of what types and fields are queryable in a GraphQL API, acting as the contract between client and server.
- Over-fetching
- — Receiving more data than needed in a response, a common REST issue that GraphQL's field-level selection avoids.
- Query complexity limiting
- — Server-side restrictions on how deep or wide a GraphQL query may be, preventing an excessively expensive query from overwhelming the server.
In an interview
Why is the N+1 problem especially easy to accidentally introduce in a GraphQL API?
- each field's resolver is written independently, with no inherent awareness of sibling fields or how many times it will be called
- a resolver for a nested field (like a user, nested under each order) runs once per parent object by default, unless explicitly batched
- the fix is typically a batching layer (like DataLoader) that collects individual lookups within a request and issues one combined query instead
Can you recall it?
Why doesn't 'one query, exactly the fields I asked for' automatically guarantee good performance in GraphQL?