“Should this be REST or GraphQL or gRPC?” is a question we’ll actually get asked at work, and definitely in interviews. In simple language — they’re three different styles of API, each great at a different job. There’s no “best”, only “best for this”.
Let’s take them one at a time, then compare.
REST — resources over HTTP
We covered REST already: resources with noun URLs, standard HTTP methods, stateless requests. It’s the default for a reason — simple, cacheable, works in any browser, understood by everyone.
Its main pain is data shape. Because each endpoint returns a fixed structure, we hit two problems:
- Over-fetching — the mobile app needs a user’s name, but
GET /users/5sends the whole fat object (address, preferences, timestamps). Wasted bytes. - Under-fetching — to render one screen we need a user and their orders and each order’s items, so we make three or four round trips. This is the classic “N+1 requests” chattiness.
For a lot of APIs, that’s totally fine. But when clients have wildly different data needs, it bites.
GraphQL — the client picks the fields
GraphQL flips it around. There’s one endpoint (POST /graphql), and the client sends a query describing exactly the fields it wants — no more, no less.
query {
user(id: 5) {
name
orders {
total
items { title }
}
}
}
One request, one round trip, exactly the fields asked for. Over-fetching and under-fetching basically vanish — that’s GraphQL’s superpower. It’s fantastic when many different clients (web, iOS, Android) each want a different slice of the same data.
The catch is on our side, the server. Because the client can ask for anything, a naive resolver for orders → items can fire a fresh database query per order — the dreaded N+1 query problem. We fix it with batching tools like DataLoader, but it’s real work. GraphQL is also harder to cache (everything’s a POST to one URL, so plain HTTP caching doesn’t apply) and needs a schema and query layer that REST just doesn’t.
gRPC — fast binary RPC
gRPC isn’t about resources at all — it’s RPC, “remote procedure call”. We define services and methods in a .proto file, and it feels like calling a local function that happens to run on another machine.
service UserService {
rpc GetUser (GetUserRequest) returns (User);
}
message GetUserRequest { int32 id = 1; }
message User { int32 id = 1; string name = 2; }
Two things make it fast. It serializes data as Protocol Buffers — a compact binary format, much smaller than JSON — and it runs over HTTP/2, which multiplexes many calls over one connection and supports streaming (client-stream, server-stream, or full bidirectional). That combo makes it excellent for chatty service-to-service traffic inside our backend, and for real-time streams.
The trade-off: it’s binary, so we can’t just eyeball it in a browser or curl it easily, and browsers can’t call it directly (they need a grpc-web proxy). That’s why gRPC lives mostly inside the datacenter, between our own services, not as a public-facing API.
Side by side
Wire: JSON / text
Transport: HTTP/1.1
Endpoints: many URLs
Cache: easy (HTTP)
Pain: over/under-fetch
Wire: JSON
Transport: HTTP (POST)
Endpoints: one
Cache: hard
Pain: N+1 resolvers
Wire: protobuf (binary)
Transport: HTTP/2
Endpoints: RPC calls
Cache: n/a
Pain: not browser-friendly
When to pick each
- Reach for REST when we want a simple, public, cacheable API that any client can consume — CRUD over resources, third-party integrations, anything a browser or curl should hit easily. It’s the safe default.
- Reach for GraphQL when many different clients need different shapes of the same data, and we’re tired of building a dozen bespoke endpoints or watching mobile over-fetch. Great for rich frontends and aggregating several data sources.
- Reach for gRPC for internal service-to-service calls where speed and efficiency matter, or when we need streaming. Two microservices chattering millions of times a day love the compact binary + HTTP/2 combo.
Real systems mix them: gRPC between backend services, and a REST or GraphQL layer at the edge for the outside world.
Interview soundbite
REST is resources over HTTP — simple and cacheable, but clients over- and under-fetch. GraphQL gives one endpoint where the client picks exactly the fields it wants (killing over/under-fetch) at the cost of caching and N+1 resolver work on the server. gRPC is binary protobuf over HTTP/2 — fast, strongly typed, supports streaming, ideal for internal service-to-service traffic but not browser-friendly. Default to REST for public APIs, GraphQL for flexible clients, gRPC for internal high-throughput calls.