REST vs GraphQL vs gRPC — Comparison
The honest trade-offs. Latency, payload size, tooling, debuggability, mobile-friendliness, where each one breaks first.
Summary#
This page is the head-to-head. Three styles — REST, GraphQL, gRPC — across the dimensions that actually matter when you’re choosing on a Tuesday afternoon for a real surface. There is no winner; there are trade-offs. The senior signal in an interview is naming the trade-offs precisely and picking per surface, not per company.
A shorter version of the answer, in one paragraph: REST is the default for public APIs and anything a browser will touch directly. gRPC is the default for internal polyglot service-to-service traffic. GraphQL fits when one backend serves many client shapes — most commonly mobile-and-web-and-partner — and the resource graph is rich enough that REST forces N+1. Most large companies run all three.
The rest of this page is the full table, the “where each one breaks first” walkthrough, and the closing rule.
Why it matters#
In an interview, the answer is rarely a single style. The answer is a defensible decision process. Interviewers watch for:
- Did you ask about the consumer first? The right style depends on whether it’s a browser, a mobile app, an internal microservice, or a partner backend. Candidates who name a style without asking are guessing.
- Did you name what each style costs? “We use GraphQL” without acknowledging the cache and auth complexity is a junior signal. Naming the cost is the senior one.
- Did you avoid picking a single style for the whole architecture? Production architectures mix styles. Insisting REST-everywhere or gRPC-everywhere is a tell that you’ve not had to live with the wrong choice.
- Did you have a story for migration? If the company already has REST and you’d add GraphQL or gRPC, what’s the dual-stack period look like? Greenfield answers are easy; brownfield answers are senior.
The closing line every interviewer wants to hear: “The right style is per-surface, not per-company.”
How it works#
The head-to-head table#
The dimensions that actually drive the decision. The cell content is the honest answer, not the marketing one.
| Dimension | REST | GraphQL | gRPC |
|---|---|---|---|
| Consumer fit | Browsers, partners, polyglot ecosystem | Mobile, web SPAs with rich UIs | Internal polyglot services |
| Wire format | JSON (typically) | JSON | Binary Protobuf |
| Transport | HTTP/1.1 or HTTP/2 | HTTP/1.1 or HTTP/2 | HTTP/2 (required) |
| Latency per call | One round-trip per resource | One round-trip per screen | Sub-millisecond local; multiplexed over HTTP/2 |
| Payload size | Whole resource — over-fetches | Client picks fields — minimal | Binary Protobuf — 3-10x smaller than JSON |
| Caching | HTTP cache, CDN, ETag, all free | App-level only (POST bodies) | App-level only (binary) |
| Tooling (HTTP layer) | Everything | Everything (POSTs) | Specialised — grpcurl, BloomRPC, Connect |
| Debuggability with curl | Trivial | Possible but awkward | No — needs grpcurl |
| Browser support | Native | Native | Needs grpc-web + proxy |
| Codegen | OpenAPI + Swagger Codegen | graphql-codegen | First-class, 10+ languages |
| Schema evolution | URL versioning + additive fields | Schema deprecation | Protobuf field-number rules |
| Streaming | None (use WebSocket or SSE) | Subscriptions (over WebSocket) | First-class, 4 modes |
| Error model | HTTP status + envelope | errors[] array; partial data | 16-code gRPC status taxonomy |
| Authorization granularity | Per endpoint | Per field (expensive) | Per method |
| Mobile-friendliness | Over/under-fetch problems | Designed for it | Works (binary helps) but tooling thinner |
| Learning curve | Low | Medium | Medium-high (Protobuf + HTTP/2) |
| Tooling for non-tech partners | Documented OpenAPI, curl, Postman | Schema-introspection, GraphiQL | Heavier; needs Protobuf and stub generation |
| Time-to-first-integration | Hours | Hours-to-days | Days |
A few cells worth expanding on:
- Payload size. The 3-10x advantage for gRPC over JSON is real on internal high-QPS traffic. On a public partner API at low volume, the difference is invisible compared to the integration cost.
- Caching. This is where GraphQL hurts the most operationally. Every query is a POST; CDNs can’t cache them by URL alone. Apollo Client cache and persisted queries (which can become cacheable GETs) help, but the contrast with REST’s free HTTP caching is real.
- Streaming. REST has none natively. GraphQL has subscriptions but they ride WebSocket. gRPC’s streaming is the cleanest of the three.
- Error model. REST’s HTTP statuses are coarse —
400covers everything from malformed JSON to semantic-invalid. gRPC’s 16-code taxonomy is more precise. GraphQL’serrors[]array sits awkwardly with HTTP status (the response is always200even on errors).
Where each one breaks first#
The opposite of marketing. Every style has a failure mode that shows up at scale.
REST’s first failure: over-fetching and under-fetching on rich UIs. A mobile screen needs the user’s name, last five orders, and the items in each order. REST forces either three round-trips (N+1 across /users, /orders, /items) or a fat response that ships every field of every object even when the screen needs three. The Stripe expand= and JSON:API include= conventions paper over it; they don’t solve it. This is the failure mode that motivated GraphQL’s invention.
GraphQL’s first failure: authorization at scale. Per-endpoint auth in REST is one decision per endpoint. Per-field auth in GraphQL is one decision per field across every type. A schema with 50 types and 500 fields means 500 permission decisions, each potentially expensive. Hasura’s row-level security helps for the database-shaped case; per-field auth in a service-fronted schema is a real engineering investment. The second GraphQL failure is N+1 in resolvers without DataLoader; the third is unbounded query depth.
gRPC’s first failure: the browser story. Browsers can’t speak gRPC natively. You ship grpc-web with an Envoy proxy, or Connect, or a REST translation layer. Either way, a “gRPC backend” usually means “gRPC backend with a REST or grpc-web edge.” Teams that picked gRPC for everything internal and then needed a customer-facing web app discover this in month three.
Each style’s second failure is worth naming too:
- REST’s second failure: bulk operations. REST is one-resource-per-request. Batch endpoints with collective verbs (
POST /orders:batchCreate) work, but partial-success semantics are awkward, and atomic-bulk doesn’t exist natively. - GraphQL’s second failure: caching. No HTTP-layer cache. Apollo Client cache and persisted queries help but require investment.
- gRPC’s second failure: opacity. Binary Protobuf means you can’t tcpdump and read a trace. The whole observability story needs gRPC-aware tooling.
Why latency comparisons are misleading#
A common interview answer says “gRPC is faster than REST.” Half-true.
For a single internal call, between two services on the same network, with a warm connection: gRPC wins by maybe 10-30%. The binary Protobuf and HTTP/2 multiplexing matter. For high-QPS internal traffic (millions of calls per second), that adds up to meaningful infrastructure savings.
For a single call from a mobile client across the public Internet: TLS handshake dominates, network latency dominates, server response time dominates. The wire format is in the noise. A REST call and a gRPC call from a mobile device are roughly indistinguishable for end-to-end latency.
For a screen that needs many resources: GraphQL wins, because it’s one round-trip. REST loses, because it’s N. gRPC sits in the middle — one call per resource, but multiplexed, so the round-trip cost is amortised over the HTTP/2 connection.
The rule: latency wins from style choice are dominated by call shape, not wire format.
Why payload-size comparisons are misleading#
Protobuf is 3-10x smaller than JSON for the same data. True in isolation.
It rarely matters on the public Internet because:
- gzip / brotli compression narrows the gap significantly on text JSON.
- Network bandwidth dwarfs payload size for the kinds of payloads APIs actually return (a 5KB JSON response is invisible on a 50Mbps connection).
- Mobile cellular is the exception — payload size matters more, but TLS-handshake and connection-establishment cost matter more still.
It matters a lot on internal high-QPS traffic: a service handling 100k QPS where each call’s payload is 10KB sees 1 GB/sec of cross-rack bandwidth on JSON vs 100-300 MB/sec on Protobuf. That’s real infrastructure cost.
The rule: payload-size wins are real on internal high-QPS traffic, mostly invisible on public APIs.
Variants and trade-offs#
The honest answer is rarely “one style.” Production architectures mix.
| Architecture | What it looks like |
|---|---|
| REST-everywhere | Public REST, internal REST. Simple, slow at the internal hop, integrator-friendly. |
| GraphQL-on-edge, REST-internal | GitHub’s public API. GraphQL gateway calls internal REST services. |
| gRPC-internal, REST-edge | Stripe, Square, most fintechs. gRPC for service-to-service, REST for the public surface. |
| gRPC-internal, GraphQL-edge | Modern mobile-first companies. Internal gRPC + GraphQL gateway that resolves into gRPC calls. |
| gRPC + Connect-edge | Newer setups using buf.build/connect. One Protobuf definition serves browser (Connect), partner (REST via Connect’s JSON variant), and internal (gRPC). |
| REST + WebSocket / SSE for streaming | Slack, Discord, Figma. REST for CRUD, WebSocket for real-time. |
| Three-style stack | REST public, GraphQL mobile, gRPC internal. The mature large-company pattern. |
The pattern that fails most often: forcing one style everywhere because it’s “consistent.” Real consistency lives at the documentation and conventions layer, not at the protocol layer. A team that uses REST publicly and gRPC internally can still have a consistent error envelope, a consistent versioning policy, a consistent auth model. The protocol is below those concerns.
When this is asked in interviews#
The question takes three forms. Handle each one this way:
“REST or GraphQL for this?”
Ask about the consumer. If it’s a browser SPA with rich screens, GraphQL is defensible. If it’s a partner backend doing CRUD, REST wins. Name the trade-off explicitly: GraphQL costs you HTTP caching and adds N+1 risk; REST costs you over-fetching and forces multiple round-trips on rich UIs.
“Should we use gRPC?”
Ask about the consumer. Internal polyglot? Yes. Browser? No — or yes with grpc-web/Connect and a translating proxy. Public partner API? Probably not — onboarding cost is too high unless the integrator is sophisticated. Name the cost: you give up curl-debuggability and free browser support; you get codegen and binary efficiency.
“What style would you pick for [system]?”
Decompose. What are the consumers (probably multiple)? Public vs internal? Browser vs mobile vs service-to-service? Then assign a style per surface and name why. Avoid picking one style for the whole system unless the system genuinely has one consumer shape.
The strongest closing line: “There’s no universal winner. The style is a function of the consumer. Most production architectures use all three — REST for the public edge, gRPC internally, GraphQL where one backend serves many client shapes.”
If the interviewer pushes for a single answer, default to: “REST for public APIs. gRPC for internal service-to-service. GraphQL where the client shape is rich enough to make REST hurt.”
Related concepts#
- Web API Architectural Styles — Overview — the full six-style catalogue including SOAP, WebSocket, webhook.
- REST — The Architectural Style — REST as an architectural style.
- RESTful API Design in Practice — REST’s practical playbook.
- GraphQL — A Query Language for APIs — GraphQL in depth.
- gRPC — Protobuf over HTTP/2 — gRPC in depth.
- The API-Design Walk-through — the seven-step interview recipe that drops out of these comparisons.