Q: REST vs gRPC vs GraphQL — picking an API style.

Answer:

The three dominate modern API design. They optimize for different things: REST for simplicity + caching, gRPC for performance + strict contracts, GraphQL for flexible client queries. Most companies use more than one.

REST

HTTP verbs map to operations on resources, identified by URLs. JSON payload by convention.

POST   /v1/orders                 # create
GET    /v1/orders/123             # read one
GET    /v1/orders?status=open     # list with filters
PATCH  /v1/orders/123             # partial update
DELETE /v1/orders/123             # delete

Strengths:

  • Universal tooling (curl, browsers, CDN).
  • Cacheable via standard HTTP (Cache-Control, ETag).
  • Easy to inspect/debug.
  • Versionable by URL or header.

Weaknesses:

  • No native streaming (long-polling, chunked, or SSE workarounds).
  • Type-loose; relies on OpenAPI/JSON Schema for contracts.
  • Multiple round-trips for related data (over-fetching / under-fetching).

gRPC

Defines services in .proto files, generates typed clients/servers in any language. Uses HTTP/2 with binary Protobuf payloads.

service OrderService {
  rpc GetOrder(GetOrderRequest) returns (Order);
  rpc ListOrders(ListOrdersRequest) returns (ListOrdersResponse);
  rpc WatchOrders(WatchRequest) returns (stream OrderEvent);   // server stream
  rpc UploadOrders(stream Order) returns (Summary);            // client stream
  rpc Chat(stream Msg) returns (stream Msg);                   // bidi
}

Strengths:

  • Strong typing, generated code.
  • Binary format → smaller, faster than JSON.
  • Native streaming (server, client, bidirectional).
  • HTTP/2 multiplexing — many calls one connection.
  • First-class deadlines, cancellation, metadata.

Weaknesses:

  • Browser support requires gRPC-Web proxy (Envoy or in-process).
  • Less universal tooling (specialized debuggers needed).
  • Schema changes require .proto distribution to consumers.

Best for: service-to-service internal APIs, mobile apps with native clients, high-throughput RPC.

GraphQL

A query language. Clients send a single query asking for exactly the fields they need across multiple resources.

query {
  order(id: 123) {
    id
    total
    customer {
      name
      email
    }
    items {
      product { name price }
      quantity
    }
  }
}

Strengths:

  • Single request fetches a deep object graph.
  • Client picks fields — no over-fetching.
  • Self-documenting via introspection.
  • Strong typing via SDL.

Weaknesses:

  • Caching is harder (no URL-keyed cache; needs persisted queries + custom cache layer).
  • N+1 query risk on the server (mitigated by DataLoader-pattern batching).
  • Authorization is per-field (more surface area).
  • "Resolver explosion" — many code paths to maintain.

Best for: aggregating data from many services for a single UI; mobile apps wanting minimal payloads; partner APIs where flexibility matters.

Comparison Table

AspectRESTgRPCGraphQL
TransportHTTP/1.1 or 2HTTP/2 (required)HTTP/1.1 or 2
FormatJSON (typical)Protobuf binaryJSON over HTTP
StreamingSSE / chunked workaroundNative server/client/bidiSubscriptions (over WebSocket usually)
ContractOpenAPI (optional).proto (required)SDL (required)
Code-genOpenAPI generatorsFirst-classApollo/Relay codegen
CachingBrowser/CDN nativeHardHard (persisted queries)
Debuggingcurl, browser dev toolsgrpcurl, BloomRPCGraphiQL, IDE plugins
VersioningURL/headerBackward-compatible .proto rulesField deprecation; never break

Versioning

REST: /v1/orders, /v2/orders — old and new run side by side.

gRPC: Protobuf evolution rules. Add fields with new tag numbers; never reuse or change types. Old clients ignore unknown fields.

GraphQL: never version; deprecate fields with @deprecated, add new ones. The schema evolves like an org chart.

Error Models

REST: HTTP status codes + JSON body.

  • 4xx client error, 5xx server error.
  • RFC 7807 "Problem Details" for structured errors.

gRPC: status codes (OK, INVALID_ARGUMENT, NOT_FOUND, ABORTED, ...).

  • Rich error model via google.rpc.Status with details.

GraphQL: always 200 OK with errors array in body. Has to define partial-success.

Streaming Comparison

NeedRESTgRPCGraphQL
One-shot request/responseNativeNativeNative
Server pushes eventsSSE or pollingserver streamsubscription
Client streams uploadsChunked transferclient streamrare (multipart)
Bidirectional chatWebSocketbidi streamsubscription

When to Pick Each

Internal service-to-service          → gRPC
Public REST API for many consumers   → REST + OpenAPI
Mobile app with constrained network  → GraphQL or gRPC (compact payloads)
Browser fetch, hit-and-run           → REST
Real-time bidirectional              → gRPC or WebSocket
Aggregation across many backends     → GraphQL (BFF pattern)

Hybrid Architectures Are Normal

Browser ──REST/GraphQL──► Edge / BFF ──gRPC──► Services ──gRPC──► Services
                                                │
                                                ▼
                                              DB / cache
  • REST or GraphQL on the edge for browser ergonomics.
  • gRPC internally for performance and contracts.

Pagination

REST: ?cursor=...&limit=... (cursor-based) or ?page=...&size=... (offset-based).

gRPC: PageToken pattern.

GraphQL: Relay Cursor Connection spec — connections, edges, page info.

Cursor-based scales; offset-based breaks past a few thousand rows.

Idempotency

POST is non-idempotent by default. All styles handle this the same way:

  • Client sends Idempotency-Key header / metadata.
  • Server stores result keyed by it; replays on retry.

See Idempotent REST APIs.

Common Mistakes

MistakeFix
Using GET for actions with side effectsVerb hygiene; CDN may "pre-fetch"
REST + no OpenAPI specDrift between docs and reality; generate from spec
gRPC over the open internet without TLSAlways use TLS; gRPC was designed assuming it
GraphQL queries 5 levels deep on every pageSet depth/complexity limits; reject expensive queries
Switching everything to GraphQL "for flexibility"Old REST endpoints + caching are still right for hot read paths

[!NOTE] Pick by who consumes your API and what they need. Internal microservices = gRPC. Diverse external consumers = REST. UI aggregating lots of data = GraphQL. There is no general winner.

Interview Follow-ups

  • "How do you do auth in gRPC?" — Metadata headers (often a JWT). Interceptors verify per-call. mTLS for service-to-service.
  • "What's HATEOAS and do you use it?" — Hypermedia in REST responses (links to next actions). Theoretically elegant; rarely worth the complexity in practice.
  • "How does GraphQL handle file uploads?" — Multipart spec; usually offload to a separate REST endpoint that returns a URL.