GraphQL
In a Nutshell
GraphQL is a query language and runtime for APIs that flips the control of what data comes back from the server to the client. Instead of many fixed REST endpoints each returning a predetermined shape, GraphQL exposes a single endpoint and a schema describing all available data as a graph. The client sends a query specifying exactly the fields it wants — no more, no less — and gets back precisely that shape in one round trip. This solves REST's over-fetching and under-fetching problems elegantly: a mobile screen that needs a user's name plus their last three order totals fetches exactly that, in one request, instead of hitting three endpoints and discarding unused fields.

How It Actually Works
The Schema and the Graph
GraphQL APIs are defined by a strongly-typed schema that describes types, fields, and their relationships as a graph:
type User {
id: ID!
name: String!
orders: [Order!]! # a User connects to Orders
}
type Order {
id: ID!
total: Float!
items: [Item!]! # an Order connects to Items
}
type Query {
user(id: ID!): User # entry point
}
The client traverses this graph in a query, and the response mirrors the query's shape exactly.
Ask for Exactly What You Need
# Query: get one user's name + the totals of their last 3 orders
query {
user(id: "42") {
name
orders(last: 3) {
total
}
}
}
// Response — precisely the requested shape, one round trip
{ "data": { "user": { "name": "Ada",
"orders": [ {"total": 42.0}, {"total": 19.5}, {"total": 88.0} ] } } }
No over-fetching (only name and total come back), no under-fetching (user + orders in one request).
The Three Operation Types
| Operation | Purpose | REST Analogue |
|---|---|---|
| Query | Read data | GET |
| Mutation | Write data (create/update/delete) | POST/PUT/PATCH/DELETE |
| Subscription | Real-time updates (server pushes) | WebSockets/SSE |
Resolvers: Where Data Comes From
Each field is backed by a resolver — a function that fetches that field's data. The GraphQL runtime walks the query, calling resolvers to assemble the response. This decouples the schema (what clients see) from the data sources (databases, other services, third-party APIs) behind it.
query → user resolver (DB) → orders resolver (DB) → total (field on order)
The schema can stitch together MANY backends into one graph.
GraphQL vs REST
| Aspect | REST | GraphQL |
|---|---|---|
| Endpoints | Many (per resource) | One |
| Response shape | Server-defined | Client-defined |
| Over/under-fetching | Common | Avoided |
| Round trips | Often multiple | Usually one |
| Caching | Easy (HTTP caching by URL) | Harder (single endpoint, POST) |
| Learning curve | Low | Higher |
| File uploads / simple CRUD | Natural | Awkward |
| Versioning | Explicit (/v2) | Evolve schema, deprecate fields |
The Costs and Pitfalls
GraphQL's flexibility introduces its own challenges:
- Caching is harder — REST caches by URL; GraphQL's single POST endpoint needs application-level or normalized client caching (Apollo, Relay).
- The N+1 problem moves server-side — naive resolvers fetch related data one-by-one; solved with DataLoader batching.
- Query complexity/abuse — a client can request deeply nested, expensive queries; needs depth limiting, cost analysis, and timeouts.
- Overkill for simple APIs — for a straightforward CRUD service, REST is simpler.
When to Choose GraphQL
Great fit:
✅ Many clients with different data needs (mobile vs web)
✅ Complex, nested, related data (social graphs, dashboards)
✅ Rapidly-evolving frontends that want flexibility
✅ Aggregating multiple backends behind one graph
Prefer REST when:
✅ Simple CRUD / resource-oriented APIs
✅ Heavy reliance on HTTP caching / CDNs
✅ File uploads, simple public APIs, low complexity

Seeing It in Action
Scenario: Why a mobile team adopts GraphQL over REST for a profile screen.
The screen needs: user's name + avatar, their 3 most recent orders
(each with total + first item's thumbnail), and unread notification count.
REST approach (under-fetching / N+1):
GET /users/42 → name, avatar (+ 20 unused fields)
GET /users/42/orders?limit=3 → 3 orders
GET /orders/{id}/items (×3) → items per order
GET /users/42/notifications/unread → count
= 6 round trips, lots of over-fetched data, slow on mobile networks.
GraphQL approach (one request, exact shape):
query {
user(id: "42") {
name
avatarUrl
unreadNotifications
orders(last: 3) {
total
items(first: 1) { thumbnailUrl }
}
}
}
= 1 round trip, only the needed fields, response shaped for the screen.
Server side: a DataLoader batches the per-order item lookups into a single
query, avoiding N+1 despite the nested shape.
Why this is transformative for the mobile team: on a high-latency mobile network, collapsing six round trips into one dramatically improves perceived performance, and fetching only the needed fields saves bandwidth and battery. Just as importantly, when the design team later adds a "loyalty points" field to the screen, the mobile client just adds loyaltyPoints to its query — no new endpoint, no backend deploy, no versioning. That client-driven flexibility, across many screens and app versions with different needs, is exactly what GraphQL is built for — at the cost of harder caching and the operational care (query limits, batching) the flexibility demands.
Interview Questions
Q: What core problems with REST does GraphQL solve? Hint: Over-fetching (REST endpoints return fixed shapes with unused fields) and under-fetching / N+1 (assembling a view requires many REST calls). GraphQL lets the client specify exactly which fields and nested relationships it wants in a single query against one endpoint, returning precisely that shape in one round trip — client-controlled response shape instead of server-dictated.
Q: What are the three GraphQL operation types? Hint: Query (read data, like GET), Mutation (write data — create/update/delete, like POST/PUT/DELETE), and Subscription (real-time server-pushed updates, typically over WebSockets/SSE). All operate against the single typed schema; queries and mutations are the everyday operations, subscriptions add live data.
Q: What are resolvers, and why do they make GraphQL good at aggregation? Hint: A resolver is a function that fetches the data for a specific field. The runtime walks the query calling resolvers to assemble the response. Because each field can resolve from any source, one schema can stitch together many backends (databases, microservices, third-party APIs) into a single unified graph — decoupling what clients see from where the data lives.
Q: Why is caching harder with GraphQL, and what about the N+1 problem? Hint: REST caches naturally by URL with HTTP caching/CDNs; GraphQL uses one endpoint (usually POST), so URL-based HTTP caching doesn't apply — you need normalized client caches (Apollo/Relay) or persisted queries. The N+1 problem moves server-side: naive resolvers fetch related items one-by-one; DataLoader batches these lookups into single queries to avoid it.
Q: When would you choose REST over GraphQL? Hint: For simple CRUD/resource-oriented APIs, when you rely heavily on HTTP caching and CDNs, for file uploads, or for simple public APIs where GraphQL's complexity (query cost control, caching setup, learning curve) isn't justified. GraphQL shines with many clients needing different data, complex nested/related data, and rapidly-evolving frontends — not everywhere.
References
- GraphQL official documentation — schema, queries, resolvers
- Apollo GraphQL docs — production client/server tooling
- How to GraphQL — comprehensive tutorial
Dive Deeper
- GraphQL at scale: caching and the N+1 problem (DataLoader) — batching related fetches
- GitHub's GraphQL API — a large public GraphQL API
- Principled GraphQL — best practices for building and evolving graphs