If you've ever sat in a technical interview and someone asked "Why would you choose GraphQL over REST?" and your brain went blank — this post is for you.
APIs are the backbone of modern software. Every app you use — from Instagram to your banking portal — communicates through APIs. But in 2026, you're no longer limited to just one flavor. The three dominant API paradigms — REST, GraphQL, and gRPC — each have distinct strengths, trade-offs, and ideal use cases.
By the end of this guide, you'll understand:
- What each API type is and how it works under the hood
- When to use each one (with real-world examples)
- Code samples you can actually run
- How to talk about API choices confidently in interviews
Let's dive in.
What Is an API, Really?
An API (Application Programming Interface) is a contract between two software systems. It defines how they can talk to each other — what requests are valid, what responses look like, and what rules must be followed.
Think of it like ordering at a restaurant:
- You (the client) look at the menu (API docs)
- You place an order (make a request)
- The kitchen (server) prepares and returns your food (response)
The three paradigms we're covering are just different "restaurant styles" — a fast food counter (REST), a custom tasting menu (GraphQL), and a highly efficient delivery pipeline (gRPC).
🌐 REST: The Workhorse of the Web
What Is REST?
REST (Representational State Transfer) is an architectural style built on top of HTTP. It organizes data around resources (nouns) and uses standard HTTP methods (verbs) to perform operations.
| HTTP Method | Action | Example |
|---|---|---|
GET | Read | Fetch a user profile |
POST | Create | Register a new user |
PUT/PATCH | Update | Edit a blog post |
DELETE | Delete | Remove a comment |
Core Principles of REST
- Stateless — Each request must contain all the information the server needs. No session memory between calls.
- Resource-based URLs — Endpoints represent resources, not actions.
/users/42is good;/getUser?id=42is not. - Uniform Interface — Consistent conventions across the API.
- Cacheable — Responses can be cached to improve performance.
REST in Action: A Blog API
// Fetch a list of posts
// GET /posts
const response = await fetch("https://api.myblog.com/posts", {
headers: {
"Authorization": "Bearer YOUR_TOKEN",
"Content-Type": "application/json"
}
});
const posts = await response.json();
console.log(posts);
// [{ id: 1, title: "Hello World", author: "Jane" }, ...]
// Create a new post
// POST /posts
const newPost = await fetch("https://api.myblog.com/posts", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_TOKEN",
"Content-Type": "application/json"
},
body: JSON.stringify({
title: "My First API Post",
content: "This is the body of my post.",
authorId: 42
})
});
const created = await newPost.json();
console.log(created);
// { id: 101, title: "My First API Post", ... }
Real-World REST Use Cases
- Stripe's Payment API — REST is simple, well-understood, and easy to document. Payment operations map naturally to CRUD resources.
- GitHub REST API — Managing repositories, issues, and pull requests via resource-based endpoints.
- Twitter/X API v1 — Fetching tweets, users, and timelines.
- Public Government Data APIs — Weather, demographics, public records.
When REST Shines ✅
- You're building a public-facing API that third parties will consume
- Your data model is straightforward and resource-oriented
- You need wide language and tooling support
- Your team is junior or mixed-experience — REST has the gentlest learning curve
- You want easy caching via HTTP headers
When REST Struggles ❌
- You need data from multiple resources in one call (leads to over-fetching or under-fetching)
- Your clients have different data needs (mobile app vs web app)
- You have deeply nested or highly relational data
🔮 GraphQL: Ask for Exactly What You Need
What Is GraphQL?
GraphQL is a query language for APIs developed by Facebook (Meta) in 2012 and open-sourced in 2015. Unlike REST, it exposes a single endpoint and lets the client specify exactly what data it wants.
This solves two critical REST problems:
- Over-fetching: Getting more data than you need
- Under-fetching: Needing multiple requests to assemble the data you need
With GraphQL, the client is in the driver's seat.
How GraphQL Works
GraphQL uses a schema to define the shape of your data and three operation types:
| Operation | Purpose |
|---|---|
query | Read data (like GET) |
mutation | Write/modify data (POST/PUT) |
subscription | Real-time data streaming |
GraphQL in Action
# Schema Definition (server-side)
type User {
id: ID!
name: String!
email: String!
posts: [Post!]
}
type Post {
id: ID!
title: String!
content: String!
author: User!
comments: [Comment!]
}
type Query {
user(id: ID!): User
posts: [Post!]!
}
type Mutation {
createPost(title: String!, content: String!, authorId: ID!): Post!
}
// Client-side: Fetch a user with only their name and post titles
// No over-fetching — we ask for exactly what we need
const query = `
query GetUserWithPosts($userId: ID!) {
user(id: $userId) {
name
posts {
title
}
}
}
`;
const response = await fetch("https://api.myblog.com/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_TOKEN"
},
body: JSON.stringify({
query,
variables: { userId: "42" }
})
});
const { data } = await response.json();
console.log(data.user);
// { name: "Jane Doe", posts: [{ title: "Hello World" }] }
// Mutation: Create a new post
const mutation = `
mutation CreatePost($title: String!, $content: String!, $authorId: ID!) {
createPost(title: $title, content: $content, authorId: $authorId) {
id
title
}
}
`;
const result = await fetch("https://api.myblog.com/graphql", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: mutation,
variables: {
title: "GraphQL is Great",
content: "Here's why...",
authorId: "42"
}
})
});
const { data: mutationData } = await result.json();
console.log(mutationData.createPost);
// { id: "102", title: "GraphQL is Great" }
Real-World GraphQL Use Cases
- GitHub GraphQL API v4 — Powers their developer portal; clients request exactly the repo data they need.
- Shopify Storefront API — Mobile and web clients request different product fields without separate endpoints.
- Facebook/Instagram — The origin story; handles massive relational data efficiently across devices.
- Netflix — Uses GraphQL as a federation layer to aggregate microservices.
When GraphQL Shines ✅
- You have multiple clients with different data needs (iOS, Android, Web)
- Your data is highly relational — users have posts, which have comments, which have likes
- You want to minimize round trips and get everything in one request
- You're building a product API (internal or BFF — Backend for Frontend)
- You want strong typing and introspection built in
When GraphQL Struggles ❌
- Simple CRUD apps — GraphQL's complexity isn't justified
- File uploads are awkward (REST handles this far more naturally)
- HTTP caching is harder — All requests go to a single endpoint via POST
- N+1 query problem — Without tools like DataLoader, nested queries can hammer your database
- Steeper learning curve for the team
⚡ gRPC: Speed and Efficiency at Scale
What Is gRPC?
gRPC (Google Remote Procedure Call) is a high-performance, open-source framework developed by Google. It uses Protocol Buffers (Protobuf) as its interface definition language and serialization format, and runs over HTTP/2.
Instead of thinking in terms of resources (REST) or queries (GraphQL), gRPC thinks in terms of calling functions on a remote server — as if they were local functions.
Why Is gRPC So Fast?
- Protobuf over JSON — Binary serialization is significantly smaller and faster to parse than JSON text.
- HTTP/2 — Supports multiplexing (multiple requests over one connection), header compression, and bidirectional streaming.
- Strongly typed contracts —
.protofiles generate client and server code automatically. - Streaming — Supports server-streaming, client-streaming, and bidirectional streaming natively.
gRPC in Action
// post.proto — The contract between client and server
syntax = "proto3";
package blog;
service PostService {
rpc GetPost (GetPostRequest) returns (Post);
rpc CreatePost (CreatePostRequest) returns (Post);
rpc StreamPosts (StreamPostsRequest) returns (stream Post);
}
message GetPostRequest {
string id = 1;
}
message CreatePostRequest {
string title = 1;
string content = 2;
string author_id = 3;
}
message StreamPostsRequest {
string author_id = 1;
}
message Post {
string id = 1;
string title = 2;
string content = 3;
string author_id = 4;
string created_at = 5;
}
// Node.js gRPC client (using @grpc/grpc-js)
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");
const packageDef = protoLoader.loadSync("post.proto", {});
const grpcObject = grpc.loadPackageDefinition(packageDef);
const PostService = grpcObject.blog.PostService;
const client = new PostService(
"localhost:50051",
grpc.credentials.createInsecure()
);
// Unary call: Get a single post
client.GetPost({ id: "101" }, (error, post) => {
if (error) {
console.error("Error:", error);
return;
}
console.log("Post:", post);
// { id: "101", title: "Hello World", content: "...", author_id: "42" }
});
// Server streaming: Stream all posts from an author
const stream = client.StreamPosts({ author_id: "42" });
stream.on("data", (post) => {
console.log("Received post:", post.title);
});
stream.on("end", () => {
console.log("Stream complete");
});
stream.on("error", (err) => {
console.error("Stream error:", err);
});
Real-World gRPC Use Cases
- Google internal services — Nearly all internal microservice-to-microservice communication at Google uses gRPC.
- Netflix — Uses gRPC for inter-service communication in their microservices architecture.
- Uber — gRPC powers real-time location updates and dispatch systems.
- Cloudflare — Uses gRPC for high-throughput internal APIs.
- Kubernetes API — etcd (Kubernetes' key-value store) uses gRPC.
When gRPC Shines ✅
- Microservices communication — Service-to-service calls where performance matters
- Real-time streaming — Chat apps, live dashboards, IoT telemetry
- Polyglot environments — Generate type-safe clients in Go, Python, Java, Rust from one
.protofile - Low-latency requirements — Gaming backends, trading systems, telemetry pipelines
- Internal APIs not exposed to browsers
When gRPC Struggles ❌
- Browser support is limited — Native gRPC doesn't work in browsers without gRPC-Web + a proxy
- Harder to debug — Binary Protobuf isn't human-readable like JSON (use tools like grpcurl or Postman)
- Overkill for simple CRUD — Adds complexity for straightforward use cases
- Smaller ecosystem than REST for public API tooling
📊 Side-by-Side Comparison
| Feature | REST | GraphQL | gRPC |
|---|---|---|---|
| Protocol | HTTP/1.1+ | HTTP/1.1+ | HTTP/2 |
| Data Format | JSON/XML | JSON | Protobuf (binary) |
| Typing | Loose (OpenAPI) | Strong (Schema) | Strong (.proto) |
| Flexibility | Fixed endpoints | Client-defined | Fixed methods |
| Performance | Good | Good | Excellent |
| Streaming | Limited (SSE) | Subscriptions | Native (4 modes) |
| Browser Support | ✅ Excellent | ✅ Excellent | ⚠️ Needs gRPC-Web |
| Caching | ✅ HTTP native | ⚠️ Complex | ❌ Manual |
| Learning Curve | Low | Medium | High |
| Best For | Public APIs, CRUD | Product APIs, BFF | Microservices, IoT |
| Tooling Maturity | ✅ Excellent | ✅ Very Good | ✅ Good |
🏗️ Real Architecture: Which to Use and When?
Scenario 1: E-Commerce Platform
You're building an online store with a web app, iOS app, and Android app. Each client needs different data — the mobile app shows fewer product fields to save bandwidth.
✅ Recommendation: GraphQL
The BFF (Backend for Frontend) pattern with GraphQL lets each client query exactly the fields it needs. One GraphQL endpoint replaces dozens of REST endpoints. Shopify, Zalando, and most modern e-commerce platforms follow this pattern.
Scenario 2: Payment Processing API
You're building a payment gateway that third-party developers will integrate into their apps.
✅ Recommendation: REST
REST is the gold standard for public APIs. It's well-understood, easy to document with OpenAPI/Swagger, supports HTTP caching, and integrates with every language and framework. Stripe, PayPal, and Square all use REST for good reason.
Scenario 3: Internal Microservices Mesh
You have 15 microservices (auth, inventory, orders, notifications, etc.) communicating with each other thousands of times per second.
✅ Recommendation: gRPC
Protobuf's binary serialization reduces payload sizes by 60–80% compared to JSON. HTTP/2 multiplexing eliminates connection overhead. Strongly typed .proto contracts prevent interface mismatches between teams. This is how Google, Netflix, and Uber handle internal communication.
Scenario 4: Real-Time Chat Application
You're building a chat app that needs to push messages to clients instantly.
✅ Recommendation: GraphQL Subscriptions or gRPC streaming
- For browser clients: GraphQL subscriptions over WebSockets
- For mobile or backend clients: gRPC bidirectional streaming
✅ Best Practices
REST Best Practices
- Use nouns, not verbs in URLs —
/usersnot/getUsers - Version your API —
/api/v1/usersto avoid breaking changes - Return proper HTTP status codes —
201 Created,404 Not Found,422 Unprocessable Entity - Use HATEOAS for discoverability where appropriate
- Document with OpenAPI (Swagger) — makes your API self-documenting
- Paginate large collections — never return unbounded lists
// Good REST response with metadata
{
"data": [...],
"meta": {
"total": 1420,
"page": 1,
"perPage": 20,
"totalPages": 71
},
"links": {
"next": "/api/v1/posts?page=2",
"prev": null
}
}
GraphQL Best Practices
- Use DataLoader to batch and cache database queries and avoid the N+1 problem
- Implement query depth limits to prevent malicious deeply-nested queries
- Apply rate limiting by query complexity, not just request count
- Use fragments to reuse common field selections
- Never expose your entire database schema — design your graph around client needs
- Paginate with Cursor-based pagination (Relay spec) for stable results
# Use fragments for reusable field selections
fragment PostPreview on Post {
id
title
excerpt
publishedAt
author {
name
avatarUrl
}
}
query FeedQuery {
featuredPosts {
...PostPreview
}
recentPosts {
...PostPreview
}
}
gRPC Best Practices
- Keep
.protofiles under version control — they are your API contract - Use semantic versioning for packages —
package blog.v1; - Set deadlines on every RPC call — don't let calls hang indefinitely
- Implement health checks using the standard gRPC health protocol
- Use TLS in production — always
grpc.credentials.createSsl()in production - Handle retries with exponential backoff — network errors are inevitable
// Always set a deadline for gRPC calls
const deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 5); // 5-second timeout
client.GetPost({ id: "101" }, { deadline }, (error, post) => {
if (error) {
if (error.code === grpc.status.DEADLINE_EXCEEDED) {
console.error("Request timed out");
}
return;
}
console.log(post);
});
🚫 Common Mistakes to Avoid
REST Mistakes
- Using verbs in URLs:
/deletePost/42→ should beDELETE /posts/42 - Ignoring HTTP status codes: Returning
200 OKwith{ "error": "Not found" }in the body is a trap - No versioning strategy: Breaking API changes without a version bump destroys integrations
- Inconsistent naming: Mixing
camelCaseandsnake_casein the same API - Returning 500 for client errors: A bad request from the client is
4xx, not5xx
GraphQL Mistakes
- Skipping query complexity limits: A single malicious query can bring down your server
- Ignoring the N+1 problem: Fetching posts with authors triggers 1 + N database queries without DataLoader
- Exposing internal implementation details: GraphQL types should model your domain, not your database tables
- Not handling errors properly: GraphQL returns
200even for errors — always check theerrorsfield in the response - Using mutations for reads: Queries are for reading, mutations are for writing — mixing them breaks caching
gRPC Mistakes
- No deadline/timeout set: A hung RPC call blocks resources indefinitely
- Ignoring backward compatibility: Changing field numbers in
.protobreaks existing clients - Not using TLS in production: gRPC over plaintext is a serious security risk
- Treating gRPC as a browser API: Remember, browsers can't use native gRPC — use gRPC-Web or a REST/GraphQL gateway for browser clients
- Forgetting error codes: Use gRPC status codes (
NOT_FOUND,INVALID_ARGUMENT, etc.) instead of custom error objects
🚀 Pro Tips
💡 Mix and match — they're not mutually exclusive. Most real-world architectures use all three. A public REST API for third-party developers, a GraphQL BFF for your own frontend clients, and gRPC for internal microservice communication is a common and powerful combination.
💡 GraphQL Federation is the future of large-scale GraphQL. Tools like Apollo Federation and GraphQL Mesh let multiple teams own their own GraphQL subgraphs, which are stitched together into a supergraph. This is how large organizations scale GraphQL without turning it into a monolith.
💡 OpenTelemetry is your observability friend. In 2026, distributed tracing with OpenTelemetry works across REST, GraphQL, and gRPC. Set it up early — debugging a chain of microservice calls without traces is a nightmare.
💡 Use Postman or Bruno for all three. Postman supports REST, GraphQL, and gRPC in one tool. Bruno is a great open-source alternative with Git-friendly collections. Learn one tool deeply rather than three separate tools.
💡 For interviews, lead with trade-offs, not preferences. Instead of saying "I prefer GraphQL," say "I'd choose GraphQL here because the mobile client has different data needs than the web client, which eliminates over-fetching, though we'd need to implement DataLoader to avoid N+1 queries." That answer demonstrates senior-level thinking.
💡 Learn gRPC even if you don't use it yet. Understanding gRPC makes you a better distributed systems engineer. It forces you to think about interface contracts, serialization, and streaming — concepts that apply everywhere.
📌 Key Takeaways
-
REST is the default choice for public APIs. It's simple, cacheable, and universally understood. Use it when your data maps naturally to resources and you need broad ecosystem support.
-
GraphQL solves over-fetching and under-fetching. It's ideal for product APIs serving multiple clients with different data needs. The single endpoint and strong schema make it powerful but require more careful design.
-
gRPC is the performance king for internal service communication. Binary serialization, HTTP/2, and native streaming make it 5–10× faster than REST for high-throughput scenarios. But it requires more tooling and isn't browser-native.
-
None is universally "best" — the right choice depends on your clients, your data shape, your team's experience, and your performance requirements.
-
In interviews, show you understand the trade-offs: caching complexity, N+1 problems, browser support, schema design, versioning strategies. These details signal experience.
-
In 2026, the trend is toward API composition — GraphQL federation for frontend-facing APIs, gRPC for the service mesh underneath, and REST for public-facing integrations.
Conclusion
APIs are one of those foundational topics where understanding the why behind each choice separates developers who just code from developers who architect solutions.
REST, GraphQL, and gRPC aren't competitors — they're tools in your toolbox. A senior engineer picks the right tool for the job. Now you know what those jobs look like.
The next time someone in an interview asks "Why REST over GraphQL?", you won't freeze. You'll walk them through the trade-offs, mention real-world examples, and explain how you'd make the decision based on the problem at hand.
That's the kind of answer that gets you hired.