If you remember five things
The whole answer in one glance. Everything below defends these five calls.
- Graph of types and resolvers. The client composes a path through a typed schema, the server runs one resolver per field. GraphQL is an orchestration layer, not a database.
- DataLoader or you die. Nested list fields fire one query per row (the N+1 problem). Batch them into one `WHERE id IN (...)` per tick or your Postgres pool melts under real data.
- You give up free HTTP caching. Everything is POST to one URL, so CDN and ETag caching are gone. You rebuild it with persisted queries, a client normalized cache, and Redis.
- The client controls query cost. One request can ask for a 10,000-node tree, so you own depth limits, complexity scoring, and pagination. REST handed you this for free.
- Auth lives in the resolver. Any field is reachable from many query paths, so you check per field and per object, never at the entry point.
1The core idea#
Start with the contrast, because that is the whole reason anyone reaches for GraphQL.
REST is a menu with fixed dishes.
- You order
GET /users/42and the kitchen sends the whole user plate, fries you did not want included. - Need the user plus their last three orders plus each order's shipping status? That is three or four round trips, or a custom endpoint someone builds and maintains.
- On a phone over flaky LTE at 150ms per round trip, four sequential calls is 600ms of dead time before the screen starts to paint.
GraphQL flips it. One request to a single endpoint, usually POST /graphql, with a query that describes the shape you want:
query {
user(id: 42) {
name
orders(last: 3) {
total
shipment { status }
}
}
}
- The server walks that tree, resolves each field, and returns JSON in the same shape.
- No extra fields, no missing data, one round trip.
- That 600ms of chained REST calls collapses into a single 150ms request.
The mental model that matters: a graph of types with resolvers.
- You define a schema: the types and their relationships.
- You define a resolver for each field: a function that knows how to fetch that one piece of data.
- The client composes a path through the graph; the server executes it.
- The schema is a contract. Both sides agree on it, it is strongly typed, so tooling validates a query before it ever hits the network.
Three operation types to name:
queryfor reads.mutationfor writes.subscriptionfor a long-lived stream, usually over WebSockets, for things like live notifications.
GraphQL gives the client power and flexibility, and hands the server a bill: cost control, per-field authorization, and a caching story you build yourself. Say that out loud and you sound like someone who has run it, not read about it.
2How it works#
Trace the request end to end. This is the "walk me through it" answer interviewers want.
The request lifecycle:
- Client sends a query string.
- Server parses it into an AST.
- Server validates it against the schema: does this field exist, are the arguments the right type.
- Server executes by calling resolvers top to bottom, left to right. Parent resolvers run first and pass their result down to children.
The thing to say out loud: resolvers are where all the real work lives.
- A field like
user.ordershas a resolver that might hit Postgres. - The nested
shipment.statusmight call a separate microservice. - GraphQL does not know or care where data comes from. It is an orchestration layer that stitches results into the client's requested shape.
That orchestration is also the trap. Consider this query:
query {
orders(last: 50) {
customer { name }
}
}
- Naive execution fetches 50 orders in one query.
- Then it calls the
customerresolver 50 times, firing 50 separate database queries. - That is the famous N+1 problem: one query to get the list, N queries to resolve a field on each item.
The fix is DataLoader.
- A batching-and-caching utility that collects all 50 customer IDs within a single tick of the event loop.
- It resolves them with one
WHERE id IN (...)query. - If you remember one implementation detail about GraphQL, make it this one. Interviewers love it.
Mutations look similar but run sequentially (queries run in parallel), because write order matters:
mutation {
placeOrder(cartId: 7) { id status }
}
Subscriptions keep a socket open.
- The server pushes an event payload each time something the client subscribed to changes.
- Underneath usually sits a pub/sub system like Redis or Kafka, so any server instance can publish an event and the right connected clients receive it.
A worked example with real numbers#
Numbers are what sell the N+1 story in the room. Make it concrete.
The setup.
- An e-commerce app renders an orders dashboard: the 50 most recent orders, each with the customer name and shipment status.
- Each Postgres round trip from the gateway costs 4ms (a healthy primary on the same VPC).
- The shipment status comes from a separate service at 20ms per call.
Reveal the answer
Naive resolution:
- 1 query to fetch the 50 orders: 4ms.
- 50 queries, one per
customerresolver call: 50 x 4ms = 200ms. - 50 calls to the shipment service: 50 x 20ms = 1000ms if serial, or about 20ms if the resolver layer fans out in parallel (assume a 100-connection pool, so one wave).
- Total on the hot path: roughly 4 + 200 + 20 = 224ms, with Postgres hit by 51 queries.
With DataLoader batching:
- 1 query for the 50 orders: 4ms.
- 1 batched query
SELECT * FROM customers WHERE id IN (...)for all 50 customer IDs: 4ms. - 1 batched call to the shipment service for all 50 order IDs: 20ms.
- Total on the hot path: roughly 4 + 4 + 20 = 28ms, with 2 Postgres queries instead of 51.
3Trade-offs#
Be honest here: GraphQL is not strictly better than REST. It moves complexity around, and naming where is the senior signal.
| Concern | REST | GraphQL |
|---|---|---|
| Over/under-fetching | Common, needs custom endpoints | Client picks exact fields |
| Round trips | Often several | Usually one |
| HTTP caching | Easy, GET plus CDN plus ETags | Hard, everything is POST to one URL |
| Error handling | HTTP status codes | Always 200, errors in body |
| Learning curve | Low | Higher, schema plus resolvers plus tooling |
| Query cost control | Per-endpoint, predictable | Client can request expensive trees |
| Versioning | /v1, /v2 | Deprecate fields, evolve schema |
The biggest thing you give up is free HTTP caching.
- With REST, a
GET /products/9is cacheable by the browser, a CDN, and a reverse proxy for nothing. The URL is the cache key. - With GraphQL, almost everything is a
POSTto/graphql, so that whole layer does not apply out of the box. - You rebuild caching at two other layers: client-side normalized caches (Apollo Client, Relay store the result keyed by type and ID) and server-side per-request caching (DataLoader) plus application caches like Redis.
The second trade-off is query cost.
- In REST, each endpoint has a known, bounded cost.
- In GraphQL, a client can write a deeply nested query, or ask for 10,000 items with nested relations, and blow up your database.
- You have to defend the server: query depth limits, complexity scoring (assign each field a cost, reject queries over a budget), pagination, and rate limiting.
- This is a real operational burden that REST mostly hands you for free.
Third, error semantics change.
- A GraphQL response is almost always HTTP 200, even on partial failure.
- One field can succeed while another returns null with an entry in the top-level
errorsarray. - That partial-success model is powerful (one dead field does not kill the page) but your monitoring cannot just count 500s. You parse response bodies.
My opinionated take on when to reach for it:
- Reach for GraphQL when you have many clients with different data needs hitting the same backend, especially mobile where bandwidth and round trips hurt.
- Stick with REST for a simple public API, heavy read traffic that benefits from CDN caching, or a small team that does not want to run a schema and resolver layer.
When I ask "REST or GraphQL," I am not looking for your favorite. I am listening for whether you name the deciding factors (number and diversity of clients, cacheability, team maturity, federation need) before you commit. Pick one and own its bill. A candidate who says "GraphQL, it is the modern choice" has just told me they argue from fandom, not judgment.
4Where it is used#
Ground the pattern in who actually runs it, then name the one pattern that matters for senior rounds.
The origin and the big adopters:
- GraphQL came out of Facebook around 2015 to solve the mobile problem: the News Feed needed deeply nested, client-specific data, and REST made the app chatty and slow on bad networks.
- GitHub shipped a public GraphQL API (their v4) so integrators ask for precisely the repo, issue, and PR fields they need in one call instead of walking a dozen REST endpoints.
- Shopify exposes a large GraphQL API for apps that extend storefronts.
- Netflix built an internal federated graph so dozens of backend teams each own a slice of one unified schema.
Federation is the pattern worth knowing for senior interviews.
- In a microservices world you do not want one giant monolithic GraphQL server that depends on every team.
- Each service publishes its own subgraph, and a gateway composes them into one supergraph.
- The gateway figures out which service owns which field and routes sub-queries accordingly.
- A
usermight come from the identity service while that user'sorderscome from the commerce service, all invisible to the client.
Where I would reach for it:
- A product with web, iOS, Android, and maybe a smartwatch client, all wanting slightly different slices of the same data, where a backend-for-frontend per platform would otherwise multiply.
- Internal developer platforms where many teams consume a shared data graph.
Where I would not:
- A webhook receiver, a file upload service, a simple CRUD admin tool.
- Anything where aggressive CDN caching of public reads is the whole performance story.
5Common pitfalls and gotchas#
Each of these has fired a real incident. Know them by name and know the fix.
The N+1 explosion.
- Covered above with numbers, but it bites teams constantly because it is invisible until load.
- A single list query looks fine in dev with 5 rows and melts in prod with 5,000.
- Always batch with DataLoader, and watch your query logs.
No cost controls.
- Shipping a GraphQL endpoint with no depth limit or complexity analysis is like opening your database to the internet.
- A careless or malicious client sends
friends { friends { friends { ... } } }and you are done. - Add limits before launch, not after the incident.
Authorization in the wrong place.
- Any field can be requested from any query path, so you cannot do auth per endpoint.
- A field might be reachable through five different queries.
- Auth has to live in the resolver (or a dedicated layer the resolvers call), checked for each field or object, every time.
- Teams that bolt auth onto the entry point leak data.
Caching assumptions carried over from REST.
- Engineers expect their CDN to just work and are surprised reads are not cached.
- Plan the strategy deliberately: persisted queries (send a hash instead of the full query so you can use GET and CDN it), client normalized cache, and server-side caches.
Treating the schema as disposable.
- It is a public contract. Breaking changes hurt.
- The GraphQL answer to versioning is additive evolution: add new fields, mark old ones
@deprecated, track field usage, and remove only when traffic hits zero. - No
/v2.
File uploads and large binaries.
- GraphQL is JSON-over-HTTP, awkward for big file transfers.
- Most teams keep uploads on a separate REST route and pass the resulting URL through the graph.
Pitching GraphQL as a pure win. If you say "it is more efficient" and stop, I know you have not run it. The tell that you have shipped it is naming the bill in the same breath as the benefit: one query instead of six, but now I own complexity limits, per-field auth, and a caching layer I build myself. Candidates who cannot name the cost are the ones who have not been paged by it.
6How it shows up in interviews#
When GraphQL appears in a system design round, the interviewer is rarely testing whether you can write a schema. They are testing whether you understand the operational cost of the flexibility it gives clients.
Expect probes like these.
"We have a mobile app that is slow because it makes 6 API calls to render one screen. What would you do?"
- Weak answer: "Use GraphQL, it is more efficient." Full stop. This tells the interviewer you have read a blog post, not run the thing.
- Strong answer: "Six chained calls at about 150ms each is close to a second of latency, so I would collapse them into one GraphQL query shaped to the screen, or a backend-for-frontend if only this one screen needs it. But that flexibility has a bill: I now own query complexity limits so a client cannot ask for an expensive tree, and I lose free HTTP caching, so I need persisted queries plus a client-side cache to get it back." Naming both the win and the cost in the same breath is the senior signal.
"Walk me through how this query gets served."
- Weak answer: "The server fetches the data and returns it." Hand-waving.
- Strong answer: "It parses the query to an AST, validates it against the schema, then executes resolvers parent-first. The nested
customerfield is an N+1 risk: 50 orders means 50 customer lookups unless I wrap it in DataLoader, which batches those into oneWHERE id IN (...)and takes it from 51 queries to 2." Tracing parse, validate, resolve and catching the N+1 unprompted is what they want to hear.
"How do you cache this?"
- Weak answer: "Put a CDN in front of it." This does basically nothing, because every request is a POST to one URL with a different body.
- Strong answer: "HTTP caching is gone because it is one POST endpoint, so I push caching to three layers: persisted queries with GET so the CDN can edge-cache by query hash, a normalized client cache like Apollo keyed by type and ID, and Redis behind the expensive resolvers." This question separates people more than any other.
"How do you stop a client from taking down the database?"
- Weak answer: "Add rate limiting." Necessary but not sufficient, because one allowed request can still be a 10,000-node query.
- Strong answer: "Depth limiting to cap nesting, complexity scoring where each field has a cost and I reject queries over a per-query budget, mandatory pagination on list fields, and rate limiting by cost rather than request count." This is the senior signal.
"REST or GraphQL for this system?"
- Weak answer: "GraphQL, it is the modern choice." Fandom, not judgment.
- Strong answer: Refuse the false binary and state the deciding factors: number and diversity of clients, read-heaviness and cacheability, team maturity, and whether you need a federated graph across teams. Then pick one and own the trade-off. Interviewers want judgment, not a favorite.
GraphQL gives the client the pen. The server pays for whatever they write.
One more signal: mentioning that GraphQL responses are HTTP 200 with errors in the body, and what that means for your dashboards (you cannot just count 500s, you parse response bodies for the errors array), tells a senior interviewer you have actually run this in production.
"Your orders query is slow under load. Where do you look first?"
"You lost CDN caching by moving to GraphQL. How do you get it back?"
"A client sends a deeply nested query. How do you stop it from taking down the DB?"
"Where does authorization live, and why not at the gateway?"
"How do you version a GraphQL API?"
What you can now explain in the room
- Why GraphQL is a graph of types and resolvers, and that it orchestrates rather than stores data.
- What the N+1 problem is, why it hides until load, and how DataLoader takes 51 queries to 2.
- Why moving to GraphQL costs you free HTTP caching, and the three layers you use to rebuild it.
- How a client can take down your database and the four controls (depth, complexity, pagination, cost-based rate limits) that stop them.
- Why authorization has to live in the resolver, not at the entry point.
- When to reach for GraphQL versus REST, stated as deciding factors rather than a favorite.
Next, try the System Design Question walkthrough on designing a mobile backend-for-frontend to see these caching and complexity decisions play out end to end.
Get a prep plan built around your GraphQL interview
Tell us the role and your interview date. We will send back a dated, day by day plan. Free.