System Design
REST vs GraphQL: Which API Style Should You Use?
Two ways to build an API, two different philosophies. REST gives you many simple endpoints; GraphQL gives you one flexible one. Here's how they really differ, with side-by-side examples and a clear guide to choosing.
Once you understand that systems talk to each other through APIs, the next question is how those APIs should be structured. For years, REST was the default answer - and it's still the most common. But GraphQL emerged to solve specific frustrations with REST, and today choosing between them is a real architectural decision.
They aren't competitors so much as two different philosophies. REST organizes an API around resources with many endpoints. GraphQL organizes it around a query language with a single endpoint. Each makes some things easier and other things harder. This guide explains both from the ground up, shows them solving the same problem side by side, and gives you a clear framework for choosing.
What is REST?
REST (Representational State Transfer) is an architectural style for APIs built directly on top of HTTP. The core idea: organize your API around resources - the "things" in your system like users, orders, or products - and give each resource its own URL (endpoint). You then act on those resources using standard HTTP methods.
A REST API for a blog might look like this:
GET /posts → list all posts
GET /posts/42 → get post #42
POST /posts → create a new post
PUT /posts/42 → update post #42
DELETE /posts/42 → delete post #42
GET /posts/42/comments → get comments on post #42
Notice the pattern: the URL identifies what (the resource), and the HTTP method identifies what to do with it (read, create, update, delete). This consistency is REST's great strength - once you know the pattern, the whole API is predictable. REST is also stateless: each request contains everything the server needs to handle it, with no memory of previous requests, which makes REST APIs easy to scale horizontally.
Pros:
- Simple and widely understood - the industry default for good reason
- Maps cleanly onto HTTP methods and status codes
- Easy to cache using standard HTTP caching (a major advantage)
- Stateless, so it scales horizontally with ease
- Excellent tooling and broad ecosystem support
Cons:
- Over-fetching: endpoints return fixed data, often more than you need
- Under-fetching: one endpoint often isn't enough, forcing multiple requests
- Many endpoints to design, document, and maintain as the API grows
- Versioning changes can be awkward (/v1/, /v2/)
What is GraphQL?
GraphQL is a query language for APIs, developed at Facebook to address specific pain points with REST. Instead of many endpoints each returning fixed data, GraphQL exposes a single endpoint where the client sends a query describing exactly the data it wants - and gets back exactly that, no more, no less.
The same blog data via GraphQL might be requested like this:
POST /graphql
query {
post(id: 42) {
title
author { name }
comments { text }
}
}
The client is saying: "Give me post 42 - but only its title, its author's name, and the text of its comments." The server returns precisely that shape. If the client only needs the title next time, it asks for only the title. The client controls the response shape, not the server.
Pros:
- Fetch exactly what you need - no over-fetching or under-fetching
- A single request can gather data that would take many REST calls
- One endpoint; the schema describes everything available
- Strongly typed schema enables great tooling and validation
- Evolves without versioning - add fields without breaking clients
Cons:
- Caching is harder - can't rely on simple HTTP caching
- More complex to set up on the server side
- Easy to write expensive queries that strain the backend
- The N+1 query problem needs careful handling
- Overkill for simple APIs with stable, predictable data needs
The core difference: over-fetching and under-fetching
The single best way to understand why GraphQL exists is to see the specific problems it solves. Imagine a mobile screen that shows a user's name, their profile photo, and the titles of their three most recent posts.
With REST, you might need several requests:
GET /users/42 → returns the full user object (name, photo,
email, address, settings... lots you don't need)
GET /users/42/posts → returns all their posts with full content
(you only wanted three titles)
Two problems are visible here. Over-fetching: the user endpoint returns the email, address, and settings you never asked for - wasted data over the network. Under-fetching: one endpoint wasn't enough, so you had to make a second request for the posts. On a slow mobile connection, multiple round-trips and bloated payloads add up to a sluggish experience.
With GraphQL, it's a single request for exactly the needed fields:
query {
user(id: 42) {
name
photo
posts(last: 3) { title }
}
}
One round-trip, and the response contains only the name, photo, and three post titles - nothing wasted. This is GraphQL's headline advantage: it eliminates both over-fetching and under-fetching by letting the client specify precisely what it wants.
Where REST pulls ahead: caching
GraphQL's flexibility comes at a real cost, and caching is the clearest example. REST works beautifully with HTTP's built-in caching. Because a GET /posts/42 request is a standard HTTP GET to a specific URL, caches at every layer - the browser, CDNs, proxies - can store and reuse the response automatically. This is a huge performance win that comes essentially for free.
GraphQL makes this much harder. Requests are typically POSTs to a single /graphql endpoint, and each query can be different, so the standard HTTP caching machinery doesn't apply. GraphQL caching is possible, but it requires more sophisticated, application-level tooling rather than the simple, universal HTTP caching REST enjoys. For systems where caching is critical - content-heavy, read-dominated, globally distributed - this tilts the decision toward REST.
The N+1 problem in GraphQL
GraphQL's flexibility can also strain the backend in a sneaky way. Consider a query asking for a list of posts and each post's author:
query {
posts {
title
author { name }
}
}
A naive server might fetch the list of posts with one query, then fetch each post's author with a separate query - one query for the posts plus N queries for the N authors. That's the N+1 query problem, and it can quietly turn a single API call into hundreds of database hits. GraphQL servers solve it with techniques like batching and "data loaders" that gather and combine these lookups, but it's a problem you must actively handle - whereas a hand-tuned REST endpoint can be written to fetch everything in one efficient query from the start.
Warning: Both styles can suffer from inefficient data fetching, but the failure shows up in different places. In REST, inefficiency tends to mean too many round-trips from the client. In GraphQL, it tends to mean too many queries on the server. Knowing where each style strains is key to using it well.
Versioning
APIs change over time, and the two styles handle change differently. REST commonly uses explicit versioning - /v1/posts, /v2/posts - so breaking changes go into a new version while old clients keep using the old one. It's clear but means maintaining multiple versions.
GraphQL is designed to evolve without versioning. Because clients request only the fields they want, you can add new fields to the schema freely - existing clients simply don't ask for them and are unaffected. Removing or changing fields still requires care (you deprecate them gradually), but the additive nature of GraphQL means you rarely need a hard version break.
A side-by-side summary
| Dimension | REST | GraphQL |
|---|---|---|
| Structure | Many endpoints, one per resource | One endpoint, query language |
| Data fetching | Fixed per endpoint (over/under-fetch) | Client picks exact fields |
| Multiple resources | Often multiple requests | One request can fetch all |
| Caching | Easy (standard HTTP caching) | Harder (needs special tooling) |
| Learning curve | Gentle, widely known | Steeper, newer concepts |
| Versioning | Explicit (/v1, /v2) | Evolves without versions |
| Best for | Simple, cacheable, stable APIs | Complex, varied client data needs |
A third option worth knowing: gRPC
REST and GraphQL aren't the only choices. gRPC is a third style, often used for internal communication between services (rather than between a client app and a server). It uses a compact binary format and a strict contract defined ahead of time, and it's extremely fast and efficient - which makes it popular in microservices architectures where services call each other constantly and every millisecond counts. Because it isn't built on human-readable JSON over standard HTTP verbs, it's less convenient for public browser-facing APIs but excellent for high-throughput service-to-service traffic. The rough division of labor in many modern systems: GraphQL or REST for the public-facing API that clients use, and gRPC for the high-performance internal calls between backend services. Knowing all three exist - and roughly when each fits - is itself a useful signal of breadth in an interview.
How to choose
There's no universally correct answer; there's a fit for the situation. Lean toward each like this:
Choose REST when:
- Your data needs are simple and predictable
- Caching is important (content sites, read-heavy public APIs)
- You want the simplest thing that works, with the broadest tooling
- Your API is resource-oriented and maps cleanly to CRUD operations
Choose GraphQL when:
- Different clients need different shapes of the same data (e.g., a web app and a mobile app with very different screens)
- You're frustrated by over-fetching and under-fetching with REST
- Clients need to gather related data in a single request
- Your data is highly interconnected (social graphs, nested relationships)
Interview: If an interviewer asks "REST or GraphQL?", the worst answer is naming one as simply "better." The strong answer names the trade-off: "GraphQL solves over-fetching and under-fetching and is great when clients need flexible, varied data - but REST is simpler and far easier to cache. For a read-heavy, cacheable public API I'd lean REST; for an app with many different client views of interconnected data, I'd consider GraphQL." Showing you understand why each exists is what demonstrates real understanding.
Seeing the responses side by side
The difference becomes vivid when you look at the actual data that comes back. Suppose a screen needs only a user's name and the titles of their recent posts.
With REST, calling GET /users/42 returns the full user resource - everything the server has, whether you need it or not:
{
"id": 42,
"name": "Aisha Khan",
"email": "aisha@example.com",
"phone": "+1-555-0142",
"address": { "city": "Austin", "country": "USA" },
"settings": { "theme": "dark", "notifications": true },
"createdAt": "2023-01-10"
}
You wanted the name. You received the name plus seven other fields - over-fetching - and you still don't have the posts, so you make a second call. That's the REST tax: fixed-shape responses and multiple round-trips.
With GraphQL, you ask for exactly the shape you want in one request:
query {
user(id: 42) {
name
posts(last: 3) { title }
}
}
And you get back precisely that - nothing more:
{
"data": {
"user": {
"name": "Aisha Khan",
"posts": [
{ "title": "Scaling our cache layer" },
{ "title": "Lessons from a migration" },
{ "title": "Why we chose Postgres" }
]
}
}
}
One request, exactly the needed fields. The response even mirrors the shape of the query, which makes GraphQL responses predictable to work with. This precision is GraphQL's whole reason for existing - and once you've felt the difference on a data-hungry mobile screen, the appeal is obvious.
Performance: not a clean winner
It's tempting to conclude GraphQL is simply faster because it fetches less, but the reality is more nuanced. GraphQL reduces over-fetching and round-trips, which genuinely helps clients on slow connections. But it shifts work to the server, which now has to parse and resolve arbitrary queries, and it gives up the easy, universal HTTP caching that makes REST so fast for read-heavy workloads. A well-cached REST API serving popular content from a CDN can easily outperform a GraphQL API that has to resolve every query freshly on the server.
The honest summary: GraphQL tends to win on flexibility and on minimizing data transfer for varied client needs; REST tends to win on caching, simplicity, and raw performance for stable, read-heavy, cacheable data. Neither is universally faster - it depends on the workload.
Real-world adoption
Both styles are thriving, which tells you neither "won." REST remains the most widely used API style by a large margin - it's the default for public APIs across the industry because of its simplicity and cacheability. GraphQL has strong adoption where its strengths matter most: companies with many client applications (web, iOS, Android, partners) consuming the same complex, interconnected data, where the flexibility to request different shapes per client is genuinely valuable. Many organizations run both - REST for simple, cacheable, public-facing endpoints, and GraphQL for the rich internal application data layer. The existence of both, side by side, in successful systems is the clearest possible evidence that this is a fit decision, not a superiority contest.
The bottom line
REST and GraphQL are tools, not rivals. REST's simplicity and cacheability make it the right default for a great many APIs - which is why, years after GraphQL arrived, it remains the most widely used style. GraphQL's flexibility makes it shine when clients have diverse, complex data needs that REST serves awkwardly. Many large systems even use both, choosing per use case rather than committing to one for everything.
The deeper skill isn't memorizing which is "best" - it's understanding the trade-offs each makes so you can match the tool to the problem in front of you. A team that reaches for GraphQL because it's newer, or clings to REST because it's familiar, is choosing by habit rather than by fit. The engineers who choose well are the ones who can articulate exactly what each style costs and buys: REST's effortless caching against GraphQL's precise fetching, REST's simplicity against GraphQL's flexibility. That kind of trade-off reasoning is the heart of system design, and we build it across System Design Fundamentals.
Go deeper
- Introduction to GraphQL - the official guide to how GraphQL works.
- REST API design - the principles and conventions behind REST.
- GraphQL (Wikipedia) - background, history, and the problems it was built to solve.
- REST (Wikipedia) - the architectural style and its constraints in depth.