REST vs gRPC vs GraphQL — when to use each. — Cracked Java
// High-Level Design (HLD / Distributed Systems) · API Design — REST, gRPC, GraphQL, WebSockets
SeniorSystem Design

REST vs gRPC vs GraphQL — when to use each.

REST vs gRPC vs GraphQL — when to use each

There is no universal winner; each optimizes a different axis. The senior answer names the axis and matches it to the call site.

The three at a glance

RESTgRPCGraphQL
TransportHTTP/1.1 or 2, JSONHTTP/2, Protobuf (binary)HTTP, JSON, single endpoint
Schemaoptional (OpenAPI)mandatory (.proto)mandatory (SDL)
Shapeserver defines resourcesserver defines RPC methodsclient picks fields
StreamingSSE / WebSockets bolt-onnative bidirectional streamssubscriptions
CachingHTTP caching "for free"none built-inhard (POST, dynamic shape)
Best atpublic, cacheable, resource APIsinternal high-throughput RPCclient-driven aggregation

REST — the default for public, resource-shaped APIs

Use REST when the domain is naturally resources, when callers are third parties or browsers, and when you want HTTP caching, intermediaries, and tooling for free. It's the most interoperable and the easiest to debug (curl, browser). The cost is over-/under-fetching (endpoints return a fixed shape) and chatty clients (one round trip per resource).

gRPC — internal, high-throughput, low-latency RPC

Reach for gRPC for service-to-service calls inside your own network. Wins:

  • Binary Protobuf is far smaller and faster to (de)serialize than JSON.
  • HTTP/2 multiplexing runs many calls over one connection; bidirectional streaming is native.
  • The .proto contract gives generated, type-safe clients in every language and disciplined schema evolution.

Costs: not natively browser-friendly (needs gRPC-Web + a proxy), binary payloads are harder to eyeball, and you lose HTTP caching. This is why the common pattern is REST/GraphQL at the edge, gRPC between internal services.

GraphQL — client-driven shape, kill the endpoint sprawl

GraphQL fits when many heterogeneous clients (web, iOS, Android, partners) each need a different slice of a graph of related data, and a REST API would either explode into custom endpoints or force over-fetching. The client sends one query describing exactly the fields it wants; the server resolves them. It excels at aggregating multiple back-end sources behind one schema.

But it carries sharp traps:

  • The N+1 problem. A query for users { posts { comments } } naively fires one query per user, then per post — a fan-out explosion. The fix is a batching/caching layer (DataLoader) that coalesces the per-field fetches into batched lookups. Always mention DataLoader.
  • Caching is hard. Queries are usually POST with a dynamic shape, so HTTP/CDN caching doesn't apply; you need persisted queries or app-level caching.
  • Cost / abuse control. A single query can be arbitrarily deep/expensive, so you need query depth/complexity limits and timeouts.
  • Over-fetch on the server. GraphQL solves client over-fetch, not server over-fetch — a sloppy resolver can still pull whole rows.
Where each protocol typically lives

How to choose, in one pass

  • Public, cacheable, resource CRUD, third-party consumers → REST.
  • Internal, latency-sensitive, high-volume, streaming, polyglot services → gRPC.
  • Diverse clients shaping their own queries over a connected data graph → GraphQL (with DataLoader + complexity limits).

Mark your status