Skip to content
beginner Phase 1 · GraphQL Foundations and REST Comparison

GraphQL and REST API Models Compared

How GraphQL's single-endpoint query model differs from REST's fixed resource endpoints.

45m
1 problems
Topic Progress 0%

The Home Screen That Wanted Four Requests

The Home Screen That Wanted Four Requests

A storefront app opens its home screen. The layout needs four things: a featured carousel (image, title, price), a cart badge with an item count, the signed-in user's first name, and three recommendations.

The frontend wires a waterfall of REST calls:

Call Endpoint What comes back What the screen renders
1 GET /products/featured 40 products, 30 fields each 4 fields of 4
2 GET /cart line items, addresses, totals, tax itemCount only
3 GET /users/me profile, addresses, preferences firstName only
4 GET /recommendations 20 products, 30 fields each 4 fields of 4

Two separate wastes appear here.

Over-fetching is response data the client never reads. The product payloads are mostly bytes that are parsed, held in memory, and dropped. On a mid-range phone that means extra JSON parsing, larger GC pressure, and more radio time for data nobody renders.

Under-fetching is the opposite failure: no endpoint matches the screen, so the client issues four requests and waits out four round trips. Latency multiplies with the number of calls, and the four responses can even arrive out of order.

GraphQL attacks both at once. One endpoint, one request, and the selection set is the UI contract:

query HomeScreen {
  featuredProducts(limit: 6) {
    id
    title
    priceCents
    imageUrl
  }
  cart {
    itemCount
  }
  me {
    firstName
  }
  recommendations(limit: 3) {
    id
    title
    priceCents
    imageUrl
  }
}

The response mirrors the query shape, so the client does no pruning and the server does not ship the 30 unused product fields.

One honest limit: GraphQL removes bytes and round trips, not computation. The server still walks every field you asked for, so a greedy query can cost more than four REST calls did. That cost is controlled later with depth limits and budgets, covered in query depth limits.

The Cache You Give Up and the One You Rebuild

The Cache You Give Up and the One You Rebuild

GET /products/featured is cacheable for free. The URL names the resource, Cache-Control and ETag set the policy, and the browser, the CDN, and every proxy in between can store the exact bytes without understanding anything about your API. This is REST's quiet advantage, and it is the single most common reason teams talk themselves out of GraphQL.

A GraphQL request is almost always POST /graphql, and HTTP caches do not cache POST bodies. The cache key is built from method and URL; the operation lives in the request body, so two different queries to the same endpoint are indistinguishable to an intermediary. GraphQL therefore needs deliberate caching design at one of three layers:

  1. GET with a query string. GET /graphql?query=query%7B...%7D is cacheable like any URL, but URL length limits (proxies often cap around 2 KB) restrict it to short queries and it produces URLs no human wants to read.
  2. Persisted queries. The client sends a hash instead of the document; the CDN keys on the hash and serves cached responses without ever seeing the text. See persisted queries.
  3. Entity-level response caching. The server caches per entity (Product:123) and assembles the response from cached pieces, so the cache survives changes in which fields a client requests. See response caching strategy.

Client libraries add a fourth layer of their own: a normalized cache keyed on __typename plus id, so the same product fetched by two different queries is stored and rendered once. That is why an id field on every object type is treated as a convention rather than a preference.

Concern REST GraphQL
Cache key method + URL must be designed (hash, entity, client cache)
Payload shape server decides client decides per request
Breaking change new URL path new schema version only after removal
Error reporting HTTP status per call status plus errors array per response
Round trips per screen one per resource one per screen

The trade is real: you buy exact payloads and fewer requests, and you pay for caching infrastructure you would otherwise get from a plain HTTP cache.

Versionless Does Not Mean Effortless

Versionless Does Not Mean Effortless

REST teams version by URL. When GET /v1/products changes shape incompatibly, you ship /v2/products, run both paths, duplicate CDN entries, and wait months for old clients to migrate. Every version doubles the surface you test.

GraphQL takes a different route: the endpoint never changes, so the schema must evolve without breaking anyone. The safe moves are additive. Adding a nullable field changes nothing for existing queries. Changing a field's type, removing a field, or tightening nullability breaks queries that already exist in the wild, so those changes go through a cycle instead:

  1. Mark the old field @deprecated(reason: "use priceCents"), which keeps it resolvable while surfacing a warning in tooling.
  2. Ship the replacement field and let clients migrate query by query.
  3. Remove the old field only after telemetry shows zero traffic.

That cycle is only trustworthy if something enforces it. Schema checks in CI compare the proposed schema against the current one and block merges that break existing operations, see schema checks in CI and schema evolution rules. Without that gate, "we just add fields" quietly turns into "someone removed one last Tuesday."

When REST is still the right answer

  • The API is a clean resource CRUD surface and responses should be cacheable at the CDN by URL.
  • The work is file upload, download, or webhook delivery, where a request body of raw bytes beats a JSON envelope.
  • There is one client, one team, and the schema would be ceremony rather than leverage.
  • Server-to-server traffic where the consumer already shares your code and needs no discoverability.

Interviewers probe exactly here. "When would you not use GraphQL?" earns a real answer when you name a concrete cost: caching-by-URL you would have to rebuild, resolver composition you would have to staff, or a governance process you would have to run. A framework that fits everywhere fits nowhere in particular.

Practice Problems

0 / 1 solved
Collapse a Four-Request Screen into One Operation
Request shaping

The home screen needs: 6 featured products (id, title, priceCents, imageUrl), the cart item count, the current user first name, and 3 recommendations with the same four product fields. Today the client calls GET /products/featured, GET /cart, GET /users/me, and GET /recommendations in sequence. Write the single GraphQL document that replaces the whole waterfall, assuming the schema exposes featuredProducts(limit: Int), cart, me, and recommendations(limit: Int).

Example:

Input: REST: 4 sequential calls, each returning full product or user records

Output: query HomeScreen { featuredProducts(limit: 6) { id title priceCents imageUrl } cart { itemCount } me { firstName } recommendations(limit: 3) { id title priceCents imageUrl } }

One request replaces four round trips, and each product selection asks for exactly the four fields the cards render instead of the full 30-field record.

Optimal Solution — O(1) network round trips time, O(f) fields selected space

Mirror the layout tree in the selection set: one top-level field per UI region, leaf fields at the leaves. Round trips drop from 4 to 1 and payload size follows the fields actually selected.

query HomeScreen {
  featuredProducts(limit: 6) {
    id
    title
    priceCents
    imageUrl
  }
  cart {
    itemCount
  }
  me {
    firstName
  }
  recommendations(limit: 3) {
    id
    title
    priceCents
    imageUrl
  }
}

Edge Cases:

  • A region that is conditionally rendered can be skipped with @include(if:) instead of sending a second request
  • If one top-level field fails, the response still carries partial data for the others

Quiz

1. A product card needs id, title, price, and image. GET /products returns 40 fields for every product and the app renders four of them. Which problem is this?

Question 1 options

2. Why does a typical POST /graphql response miss the CDN cache that GET /products/featured gets for free?

Question 2 options

3. Which schema change can ship without a new URL version because it cannot break an existing query?

Question 3 options

4. A public, read-heavy API must let any proxy cache responses by URL. Which statement is correct?

Question 4 options

Flashcards

Question

Over-fetching vs under-fetching

Answer

Over-fetching: response contains fields nobody reads (wasted bytes). Under-fetching: one response is not enough, so the client issues more requests (wasted round trips).

Question

Why a POST /graphql response is not cached by default

Answer

HTTP cache keys use method + URL; the operation is in the body, so identical URLs hide different queries. Fix: query-string GET, persisted-query hashes, or entity-level caching.

Question

How GraphQL avoids /v2 URL versioning

Answer

Evolve additively: add nullable fields, deprecate old ones with @deprecated, remove only after traffic shows migration. Enforced by schema checks in CI.

Question

What GraphQL does NOT reduce

Answer

Server-side work. It cuts payload bytes and round trips; a deep query still costs resolver work, so depth and cost limits are required.

Question

When to keep REST

Answer

Clean resource CRUD that must cache by URL, file transfer and webhooks, one-client APIs, or teams with no one to own schema governance.

Revision Notes

Key Takeaways

  • 1. REST fixes the payload shape in the server; GraphQL moves it into the client selection set, which removes over-fetching and under-fetching in one request
  • 2. Caching by URL is free in REST and must be rebuilt in GraphQL through persisted queries, GET query strings, or entity-level caches
  • 3. Versionless APIs depend on additive changes plus a deprecation window backed by CI schema checks
  • 4. GraphQL loses when cache-by-URL, file transfer, or simple CRUD dominate the requirements

Interview Tips

  • • Answer "when would you not use GraphQL" with a specific cost (CDN caching you would rebuild, resolver composition to staff, governance to run) instead of a vague pros and cons list
  • • Walk a concrete screen: count the REST round trips, name the unused fields, then show the single query that replaces them

Cheat Sheet

REST vs GraphQL cheat sheet

Shapes

  • REST: URL names the resource, server picks fields -> over-fetching
  • GraphQL: POST /graphql, client picks fields in a selection set -> one round trip per screen

Caching

  • GET + Cache-Control/ETag = free CDN caching
  • POST bodies are not cacheable -> persisted queries, query-string GET, entity cache

Evolution

  • REST: breaking change = new path (/v2), dual maintenance
  • GraphQL: add nullable field -> @deprecated -> remove after migration; block breaks in CI

Keep REST when: CRUD + cache-by-URL, uploads/downloads, webhooks, single client.

Continue Learning

Before You Start

  • No prerequisites listed.

Related Reading