REST vs GraphQL vs gRPC — Comparison

The honest trade-offs. Latency, payload size, tooling, debuggability, mobile-friendliness, where each one breaks first.

Concept Intermediate
10 min read
rest graphql grpc comparison api

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.

DimensionRESTGraphQLgRPC
Consumer fitBrowsers, partners, polyglot ecosystemMobile, web SPAs with rich UIsInternal polyglot services
Wire formatJSON (typically)JSONBinary Protobuf
TransportHTTP/1.1 or HTTP/2HTTP/1.1 or HTTP/2HTTP/2 (required)
Latency per callOne round-trip per resourceOne round-trip per screenSub-millisecond local; multiplexed over HTTP/2
Payload sizeWhole resource — over-fetchesClient picks fields — minimalBinary Protobuf — 3-10x smaller than JSON
CachingHTTP cache, CDN, ETag, all freeApp-level only (POST bodies)App-level only (binary)
Tooling (HTTP layer)EverythingEverything (POSTs)Specialised — grpcurl, BloomRPC, Connect
Debuggability with curlTrivialPossible but awkwardNo — needs grpcurl
Browser supportNativeNativeNeeds grpc-web + proxy
CodegenOpenAPI + Swagger Codegengraphql-codegenFirst-class, 10+ languages
Schema evolutionURL versioning + additive fieldsSchema deprecationProtobuf field-number rules
StreamingNone (use WebSocket or SSE)Subscriptions (over WebSocket)First-class, 4 modes
Error modelHTTP status + envelopeerrors[] array; partial data16-code gRPC status taxonomy
Authorization granularityPer endpointPer field (expensive)Per method
Mobile-friendlinessOver/under-fetch problemsDesigned for itWorks (binary helps) but tooling thinner
Learning curveLowMediumMedium-high (Protobuf + HTTP/2)
Tooling for non-tech partnersDocumented OpenAPI, curl, PostmanSchema-introspection, GraphiQLHeavier; needs Protobuf and stub generation
Time-to-first-integrationHoursHours-to-daysDays

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 — 400 covers everything from malformed JSON to semantic-invalid. gRPC’s 16-code taxonomy is more precise. GraphQL’s errors[] array sits awkwardly with HTTP status (the response is always 200 even 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.

ArchitectureWhat it looks like
REST-everywherePublic REST, internal REST. Simple, slow at the internal hop, integrator-friendly.
GraphQL-on-edge, REST-internalGitHub’s public API. GraphQL gateway calls internal REST services.
gRPC-internal, REST-edgeStripe, Square, most fintechs. gRPC for service-to-service, REST for the public surface.
gRPC-internal, GraphQL-edgeModern mobile-first companies. Internal gRPC + GraphQL gateway that resolves into gRPC calls.
gRPC + Connect-edgeNewer 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 streamingSlack, Discord, Figma. REST for CRUD, WebSocket for real-time.
Three-style stackREST 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.”

Search ESC

Keyboard shortcuts

Shortcuts are disabled while typing in inputs.