GraphQL vs REST: Key Differences, Pros, Cons, and When to Choose Each
GraphQL and REST are both popular ways to build APIs, but they solve different problems and make different trade-offs. Knowing when to choose each is a mark of a well-rounded backend developer β and it comes up constantly in technical interviews.
This guide gives you a thorough, honest comparison.
What Is REST?
REST (Representational State Transfer) is an architectural style where resources are exposed as URLs, and HTTP methods (GET, POST, PUT, DELETE) define the actions.
codeGET /users/42 -- get user GET /users/42/posts -- get posts for user POST /users -- create user PATCH /users/42 -- update user DELETE /users/42 -- delete user
Each endpoint returns a fixed shape of data defined by the server.
What Is GraphQL?
GraphQL is a query language for APIs. Instead of multiple endpoints, there is a single endpoint where the client specifies exactly what data it needs.
graphqlquery { user(id: "42") { name email posts(last: 3) { title publishedAt } } }
The server returns exactly that structure β nothing more, nothing less.
The Core Problems GraphQL Solves
Over-fetching
With REST, endpoints return a fixed payload. If you only need a user's name for a profile card, you still get the full user object including address, preferences, and billing info.
codeGET /users/42 -- Returns: { id, name, email, address, phone, preferences, billingInfo, ... } -- You needed: { name }
GraphQL:
graphqlquery { user(id: "42") { name -- only this field, nothing else } }
Under-fetching (The N+1 Problem)
To display a list of posts with author names in REST, you often need multiple requests:
codeGET /posts -- get 20 posts GET /users/1 -- get author for post 1 GET /users/2 -- get author for post 2 -- ... 18 more requests
GraphQL resolves this in one query:
graphqlquery { posts(first: 20) { title author { name avatar } } }
One round trip, all data.
Side-by-Side Comparison
| Feature | REST | GraphQL |
|---|---|---|
| Endpoints | Multiple (/users, /posts) | Single (/graphql) |
| Data shape | Fixed by server | Defined by client |
| Over-fetching | Common | Eliminated |
| Under-fetching | Requires multiple requests | Single query |
| Type system | Optional (OpenAPI/Swagger) | Built-in, strongly typed |
| Versioning | URL or header (/v1/, /v2/) | Evolve schema, deprecate fields |
| Caching | HTTP caching (GET requests) | Complex β requires client-side cache |
| File uploads | Simple | Complex (requires multipart) |
| Real-time | Requires polling or WebSockets | Built-in Subscriptions |
| Learning curve | Low | Higher |
| Tooling | Mature, universal | Growing, excellent (Apollo, etc.) |
GraphQL Strengths
Strong type system
Every field in a GraphQL schema has a type. The schema is the contract between client and server:
graphqltype User { id: ID! name: String! email: String! posts: [Post!]! createdAt: DateTime! } type Post { id: ID! title: String! content: String! author: User! publishedAt: DateTime } type Query { user(id: ID!): User posts(first: Int, after: String): PostConnection! }
The ! means non-null. Clients know exactly what fields exist and their types before writing a single line of code.
Introspection and tooling
GraphQL APIs are self-documenting. Any client can query the schema itself to discover types and fields. This powers tools like GraphiQL (interactive browser IDE) and automatic TypeScript type generation.
No versioning needed
In REST, adding a field to a response is safe, but removing one is a breaking change requiring a new version. In GraphQL, you add new fields freely and deprecate old ones with @deprecated. Clients keep working until they migrate.
graphqltype User { name: String! fullName: String! @deprecated(reason: "Use name instead") }
Subscriptions for real-time
GraphQL Subscriptions provide a standardized way to push updates to clients:
graphqlsubscription { messageAdded(channelId: "general") { id text sender { name } } }
REST Strengths
Simple HTTP caching
REST GET requests can be cached at every layer β browsers, CDNs, reverse proxies β without any extra work. GraphQL queries (usually POST) bypass most HTTP caches by default.
Simplicity
REST is easy to understand, easy to consume with any HTTP client, and requires no special library. Every developer already knows how to call a REST API.
File handling
REST handles file uploads trivially with multipart/form-data. GraphQL requires either a separate upload endpoint or complex multipart spec support.
Mature ecosystem
REST tooling (Swagger/OpenAPI, Postman, Insomnia) is universal. Every framework, every language has battle-tested REST support.
Real-World Guidance
Choose REST when:
- Building a public API consumed by many different clients
- Your data model maps cleanly to resources
- You need simple HTTP caching for performance
- The team is small or unfamiliar with GraphQL
- You are building a simple CRUD service
Choose GraphQL when:
- Multiple clients (mobile app, web app, third-party) need different data shapes
- Your data graph is complex and deeply nested
- You want to consolidate multiple microservice calls into one query
- You need real-time features and want Subscriptions
- You want automatic TypeScript types from your schema
Use both: Many companies run REST for public APIs and GraphQL for internal frontend consumption. The BFF (Backend for Frontend) pattern often uses GraphQL to aggregate multiple REST/microservices into a tailored API for each client type.
Common Interview Questions
Q: What is the N+1 problem in GraphQL?
When resolving a list of items, each item's related data is fetched separately β one query for the list plus N queries for related items. The DataLoader pattern solves this by batching and caching database calls per request.
Q: How does GraphQL handle errors?
Unlike REST which uses HTTP status codes, GraphQL always returns 200 OK (even on errors). Errors appear in an errors array alongside any partial data:
json{ "data": { "user": null }, "errors": [{ "message": "User not found", "path": ["user"] }] }
Q: What is a resolver?
A resolver is a function that returns data for a specific field in the schema. Every field can have its own resolver. If a resolver is not defined, GraphQL uses a default that looks for a property with the same name on the parent object.
Practice API Knowledge on Froquiz
API design and architecture questions appear in backend interviews at all levels. Test your backend knowledge on Froquiz across REST, databases, Node.js, and more.
Summary
- REST exposes resources via URLs; GraphQL exposes a single endpoint with typed queries
- GraphQL eliminates over-fetching and under-fetching β clients request exactly what they need
- REST caching is simple and universal; GraphQL caching requires extra work
- GraphQL's built-in type system and introspection enable powerful tooling
- REST is simpler, lower learning curve, universally understood
- GraphQL shines for complex data graphs, multiple client types, and real-time features
- Both are valid β many production systems use both together