REST vs GraphQL: What Every System Design Interview Tests

June 3, 20269 min read
system-designinterview-prepcareeralgorithms
REST vs GraphQL: What Every System Design Interview Tests
TL;DR
  • Over-fetching and under-fetching are the core problem GraphQL solves: mobile clients get exactly the fields they need, not a 40-field payload every request.
  • REST caching is essentially free; GraphQL requires persisted queries to work with CDNs, adding build-step and query-registry overhead.
  • The N+1 problem hits every GraphQL server without DataLoader: fetching ten users triggers ten separate post queries unless you batch them explicitly.
  • GraphQL versioning wins: additive schema evolution with @deprecated eliminates the parallel /v1 and /v2 endpoints REST forces you to maintain.
  • Neither REST nor GraphQL has a clean real-time story; WebSockets or SSE is the answer regardless of which API style you pick.
  • The strongest interview answer is hybrid: GraphQL for the client-facing layer where product teams need different data shapes, REST or gRPC between internal microservices.
  • Justify from requirements, not preference: client diversity, caching needs, and team size are the three trade-offs that decide the call.

You're designing a social feed. The interviewer asks how clients will get data. You say "REST endpoints." They nod. Then: "What if the mobile app only needs the user's name and avatar, but the desktop app needs twenty fields?"

Congratulations. You just walked into the actual question. Not "define REST" or "explain GraphQL." Which one do you choose when your clients have radically different data needs, and can you defend it in 45 minutes without sweating through your shirt?

Here's the answer.

What You're Actually Choosing Between

REST treats your API as a collection of resources. Each URL is a noun: /users/42, /posts/17/comments. HTTP verbs do the work: GET fetches, POST creates, PUT updates, DELETE removes. The server sends back whatever that endpoint always returns, whether you wanted it all or not.

GraphQL is a single endpoint that accepts a query language. You tell the server exactly which fields you want, and it sends back exactly those. One endpoint, one POST, shaped response. You're driving. The server does what you say.

Neither is universally better. Any interview answer that doesn't start with trade-offs is wrong before it gets going.

You Either Fetch Too Much or Not Enough

Suppose you're building a mobile profile screen. It shows the user's name, avatar, and bio. Your REST endpoint GET /users/42 returns 40 fields: email, phone, address, preferences, account history, payment methods, notification settings, the last four IP addresses they logged in from. Your mobile client uses three of them.

That's over-fetching: paying for 40 fields when you needed 3, on every single request. On a fast connection this is just wasteful. On a 2G connection in rural India it's unusable. You're shipping a truckload of bubble wrap to deliver a USB stick.

The flip side is under-fetching. Your dashboard needs a user's profile, their five most recent orders, and the product details for each order item. With REST, that's three sequential round trips: one for the user, one for the orders, one (or five) for the products. Each call depends on IDs from the previous one. At 80ms per trip you're sitting at 240ms minimum before you've rendered anything.

GraphQL collapses this into one request. You write a query for user { name orders(last: 5) { items { product { name price } } } } and get back exactly what you described. One trip. Done.

GitHub moved to GraphQL for their public API because their iOS app was making six to eight round trips per page load. After switching: one. Load times dropped over 30 percent.

The Caching Trade-off (REST Wins Here)

GraphQL evangelism tends to skip over one inconvenient fact: REST caching is essentially free, and GraphQL caching is genuinely hard.

REST GET requests work with HTTP natively. Browsers cache them. CDNs cache them. Add Cache-Control: max-age=300 to your user profile endpoint and your CDN handles millions of identical requests without your servers ever seeing them. ETags let clients check freshness for almost nothing. Thirty years of web caching infrastructure, ready to go.

GraphQL sends all queries as HTTP POST to a single endpoint. POST requests aren't cached anywhere by default. Your CDN sees the same URL for a query asking for a username and a query asking for someone's full payment history. It can't tell them apart, so it caches neither.

The workaround is persisted queries: pre-register your queries on the server, and clients send a hash ID instead of the full query string at runtime. The hash is static, so you can use a GET and CDNs cache it normally.

This works, but it adds a build step, a query registry, and coordination between frontend and backend deploys. For a small team iterating fast, that overhead is real. For a public API serving billions of identical reads, REST's caching story is just better.

Read more on the caching patterns themselves in Caching Strategies for System Design.

GraphQL's Hidden Cost: The N+1 Problem

There's a performance trap that bites almost every team that adopts GraphQL without knowing what's coming.

GraphQL resolvers execute per field. Request a list of ten users and their posts, and GraphQL calls a resolver for each user to fetch their posts. That's 1 query for the users, then 10 individual queries for posts. Eleven queries where two would do. Now do that at 100 users. At 1,000. Your database is getting a very unpleasant surprise.

The fix is DataLoader, a batching utility that collects all the individual .load(userId) calls within a single execution tick, then fires one SELECT * FROM posts WHERE user_id IN (...) instead of ten separate queries. It also memoizes results within the request so the same ID isn't fetched twice.

Every production GraphQL server needs DataLoader or something equivalent. Shopify runs over one million GraphQL queries per second in production. They published their batching approach because they learned this lesson the hard way, at scale, in a way nobody wanted to learn anything.

The good news: DataLoader is a solved problem. The bad news: it doesn't wire itself up. You have to do it for every relationship in your schema. Miss one, and you get the quiet performance cliff that only shows up under load.

Versioning: GraphQL Wins Cleanly

How do you evolve a REST API without breaking existing clients?

The industry settled on URL versioning: /api/v1/users and /api/v2/users. Breaking change? Bump the version. Every client declares the version they want. You run both in parallel until all clients migrate. For a big API that means maintaining v1, v2, and v3 simultaneously, indefinitely, with all three sets of tests, documentation, and on-call pager duty.

GraphQL takes a different approach entirely. The schema evolves additively. Add new fields whenever you want, and old clients keep querying the old ones untouched. When a field is stale, mark it @deprecated with a reason. Clients see a warning in their tooling. Eventually you drop it, and no one had to coordinate a migration window.

GraphQL enables continuous API evolution rather than batched version releases. GitHub's GraphQL API has shipped breaking-change-free for years by adding fields and deprecating old ones, with no forced client migrations. That's the dream of any platform team.

Real-Time: Neither Has a Clean Answer

Neither REST nor GraphQL has an elegant built-in real-time story. Both punt it.

REST relies on webhooks, long polling, or Server-Sent Events. These all work. They're all kind of ugly. Long polling in particular is exactly what it sounds like: the client asks the server "anything new?" on repeat, like a kid in the backseat asking if we're there yet.

GraphQL adds Subscriptions: a real-time stream attached to the schema. Clients subscribe to events using the same query language as everything else. The transport is usually WebSockets, though Server-Sent Events is gaining ground because it's HTTP-native and survives corporate firewalls that close WebSocket connections for fun.

If your system depends heavily on live updates, you'll reach for WebSockets or SSE regardless of which API style you pick. The choice between REST and GraphQL doesn't really change that math. See WebSockets vs Long Polling vs SSE for the detailed breakdown.

The Decision Table

DimensionRESTGraphQL
Multiple clients, different data needsPainfulNative
HTTP caching and CDN integrationNativeRequires persisted queries
Type safety and self-documentationNeeds OpenAPI/SwaggerBuilt-in schema + introspection
File uploadsNative multipartUse a separate REST endpoint
API versioningURL-based (/v1, /v2)Additive schema evolution
Public API surfaceIndustry standardLess familiar to consumers
Real-time updatesWebhooks / SSESubscriptions (WebSocket or SSE)
N+1 query riskNot a concernRequires DataLoader
Simple CRUDSimple to implementMore infrastructure to set up
Bandwidth-constrained clientsOver-fetch riskPrecise field selection

When to Use GraphQL vs REST in a System Design Interview

Your API choice should follow from requirements, not from whichever acronym you typed more recently. These are the signals that justify GraphQL:

Multiple clients with different data needs. Mobile app, web app, and third-party integration all hitting the same backend. That's the canonical GraphQL scenario. Each client asks for exactly what it needs.

Bandwidth-constrained clients. Mobile apps on poor networks, IoT devices, anything where payload size directly affects user experience. GraphQL's field selection reduces payload sizes 30 to 60 percent in real deployments.

Rapidly evolving schema. Teams shipping independently, product changing fast. GraphQL's additive evolution beats coordinated REST version bumps every time.

Deep data aggregation. User, their orders, each order's items, each item's product, each product's vendor. One GraphQL query beats a REST waterfall by a lot.

REST wins on: public APIs for external developers who don't want to learn a query language, simple CRUD with uniform data shapes, systems that need heavy CDN caching, or small teams who don't want DataLoader and persisted query infrastructure before they've shipped a single feature.

The Hybrid Answer Interviewers Respect

The strongest interview answer usually isn't "REST" or "GraphQL." It's knowing where each one lives in your system.

A common production pattern at companies like Netflix and Airbnb: GraphQL at the client-facing layer, where multiple product teams need different data shapes from the same services. REST or gRPC between internal microservices, where you control both ends and simplicity actually matters.

You can also add a GraphQL gateway that translates client queries into REST calls to existing downstream services. This is how most migrations actually happen. You don't rewrite the entire backend on a Thursday afternoon. You add a translation layer, clients get the GraphQL experience, and backend services keep their REST contracts until you have time to care.

When the interviewer asks "REST or GraphQL?" they're watching whether you explain trade-offs or just pick a favorite. Walk through client diversity, caching needs, and team size. Then commit to a choice and justify it from those requirements. That's what earns the hire signal.

If you want to practice reasoning through trade-offs like this out loud before your actual interview, SpaceComplexity runs voice-based system design mock interviews with rubric-based feedback on exactly this kind of architectural reasoning.

Further Reading