Representative interview topic

Backend Interview: When Should You Choose REST or gRPC?

BackendHard
Offer.cc Editorial TeamPublished Updated

Question

A platform needs a public API for browsers, mobile apps, and partners; an internal service interface handling 20,000 calls per second with unary and server-streaming methods; and an endpoint for third-party callbacks. Which boundaries should use REST over HTTP with JSON, and which should use gRPC? Cover contracts, compatibility, streaming, deadlines, retries and idempotency, observability, security, evolution, and validation.

Prompt and Applicable Context

A platform has three API boundaries. Browsers, mobile apps, and external partners consume the first, so it must be easy to integrate, debug, and evolve independently. The second is service-to-service traffic in a controlled data-center environment. It peaks at 20,000 calls per second and needs both ordinary request-response calls and a server stream. The third receives webhook callbacks from external systems.

The 20,000 calls per second figure is an interview assumption, not a universal performance threshold. Choose REST-style HTTP with JSON, native gRPC, or a justified combination for each boundary, then explain migration and validation. REST is an architectural style and is not tied to JSON or HTTP/1.1. “REST/HTTP+JSON” simply fixes the common implementation being compared in this prompt. gRPC is not a switch that automatically makes an operation fast, idempotent, or reliable.

This is a backend question because its core is service interfaces, protocol semantics, client contracts, and production governance. It does not ask for an entire business system, and a memorized “gRPC is faster; REST is more compatible” table is not enough.

What the Interviewer Evaluates

The first signal is whether the candidate starts with consumers and network boundaries. Public browsers and partner ecosystems value ubiquitous HTTP tooling, readable payloads, caching semantics, and low integration cost. Controlled internal callers can more readily standardize .proto files, generated code, proxies, and load balancing. The protocol can follow the boundary; one platform need not expose only one interface style.

The second signal is separating abstractions from implementations. REST uses resources, HTTP methods, status codes, and caching semantics, and it can run over HTTP/2 or HTTP/3. gRPC centers on services and methods, uses Protocol Buffers as its default interface and message definition, and provides unary, client-streaming, server-streaming, and bidirectional-streaming methods. “REST only uses HTTP/1.1” and “REST cannot stream” are both faulty shortcuts.

The third signal is completing the contract and failure model. OpenAPI can give an HTTP API a machine-readable contract and code generation. Protobuf binary compatibility does not guarantee application compatibility. Either choice still requires deadlines, cancellation, idempotency, retryable-error rules, authentication, authorization, version evolution, and observable request identity.

Finally, strong candidates ask for evidence. They benchmark representative payloads, concurrency, compression, connection behavior, and failures, then canary the result while measuring tail latency and errors. A platform migration justified only by “binary is faster” is not reproducible engineering.

Questions to Clarify Before Answering

  • Who controls clients and upgrades? Services inside one organization can coordinate generated-client releases. Partners that cannot be forced to upgrade need a stable boundary that is easy to consume independently.
  • Is the interaction unary, streaming, or asynchronous notification? Ordinary CRUD does not automatically benefit from gRPC. An ordered stream over a long-lived connection may fit native gRPC. A third-party webhook is initiated by someone else and normally must follow that party's published HTTP contract.
  • Must a browser call the service directly? Browsers cannot directly provide all the HTTP/2 control required by native gRPC. gRPC-Web, JSON transcoding, or a BFF adds a layer that changes debugging, streaming capabilities, and operations.
  • What is the actual performance problem? Changing serialization will not remove a database bottleneck, downstream fan-out, or an unbounded query. Ask for payload sizes, QPS, concurrency, p95/p99, CPU, and network budgets.
  • What do the current gateway and observability stack support? Proxy support for gRPC status, streams, health checks, and end-to-end trace correlation directly changes rollout risk.
  • How must the interface evolve? Public APIs need a compatibility policy. Internal Protobuf needs field-number and mixed-version rules. Without cross-version tests, a strongly typed contract can still fail during a rolling deployment.

30-Second Answer Framework

“I would choose per consumer boundary, not make one platform-wide choice. The browser, mobile, and partner API starts as REST-style HTTP with JSON, governed by OpenAPI, HTTP semantics, and a compatibility policy. The third-party webhook also follows its public HTTP contract. For the controlled internal path at 20,000 calls per second, I would choose native gRPC if representative benchmarks show serialization or connection costs matter and the server stream is a real requirement. Generated clients do not remove the need to set deadlines, propagate cancellation, retry only safe operations, and preserve field compatibility. At the edge, JSON transcoding or a thin gateway can share one domain implementation. Before rollout, I would compare end-to-end p99, CPU, bytes, and failure recovery with real payloads, then migrate by caller. The protocol does not replace auth, idempotency, or observability.”

Step-by-Step Deep Dive

Step 1: Decide each boundary separately

Use REST-style HTTP with JSON for the public boundary. Resource URIs, methods, status codes, conditional requests, and caching are widely understood by browsers, CDNs, command-line tools, and partners. OpenAPI can be the contract source for documentation and generated SDKs, so describing REST as “untyped and handwritten” is unfair. The cost is that the team must actively govern its error model, pagination, open enums, and specification drift.

Choose gRPC for the internal boundary only after two gates pass: callers and servers can standardize generated code and runtime infrastructure, and a representative benchmark shows enough benefit to pay for proxy, debugging, and mixed-version complexity. This prompt also has a real server-streaming requirement. gRPC's streaming methods and per-call metadata, status, and deadline form a coherent model for it. The 20,000-QPS number alone does not make the decision.

Use an HTTP webhook for the third-party callback. The external sender controls the protocol, and the public receiver needs TLS, signature verification, quick acknowledgment, and asynchronous processing. Replacing the receiver with gRPC does not make a partner that sends HTTP POST requests able to call it. If the internal processing path uses gRPC, the webhook adapter verifies and persists the event before invoking that path.

BoundaryInitial choicePrimary reasonMain cost
Browser, mobile, and partnersREST/HTTP+JSONBroad compatibility, HTTP semantics, low integration costContract and client differences need governance
Controlled internal servicesgRPCGenerated contract, streaming methods, compact messagesTooling, proxy, and rolling-upgrade complexity
Third-party callback ingressHTTP webhookSender contract and Internet interoperabilitySignature, deduplication, and async isolation are application work

Step 2: Write executable contracts

The public read interface uses resource and HTTP semantics:

http
GET /v1/orders/ord_123
If-None-Match: "order-v7"

200 OK
ETag: "order-v7"
Content-Type: application/json

The internal interface defines actions and messages:

proto
service OrderService {
  rpc GetOrder(GetOrderRequest) returns (Order);
  rpc WatchOrder(WatchOrderRequest) returns (stream OrderEvent);
}

message GetOrderRequest {
  string order_id = 1;
}

Both contracts need authentication, authorization, errors, pagination or stream-termination rules, size limits, and audit fields. A gRPC method name can hide network cost, but callers must still treat it as a remote operation with latency, timeouts, and partial failure. REST POST is not automatically idempotent either; create operations need a stable idempotency key or a business uniqueness constraint.

Step 3: Design failure semantics

By default, a gRPC client may have no deadline, so set one from the end-to-end budget. Deadline expiry makes the caller stop waiting, but the server application remains responsible for stopping work it spawned. Deadline and cancellation propagation must also be verified for the selected language and framework. HTTP clients likewise need connection, response, and total budgets; socket defaults are not business SLOs.

Retry decisions come from business semantics. HTTP GET, HEAD, PUT, and DELETE have safe or idempotent properties in the specification, but implementations must honor those method semantics. POST is safe to retry only when an idempotency key or equivalent mechanism makes the outcome safe. A gRPC method name gains no automatic idempotency; the contract must identify retryable statuses and operations. Both approaches need attempt caps, remaining-deadline checks, and protection against retries stacking in the gateway, SDK, and application.

A streaming RPC adds slow-consumer handling, backpressure, per-message limits, resume positions, and duplicate events. If consumers must resume from a cursor, events need stable IDs or sequence numbers. Reopening a stream alone cannot prove there are no gaps or duplicates.

Step 4: Make contracts survive rolling evolution

REST/JSON compatibility includes structure and semantics. Adding a response field is safe only if clients tolerate unknown fields. Changing default pagination, ordering, or enum meaning can leave JSON parseable while breaking the application. Gate releases with OpenAPI diffs, the last public SDK, recorded-request replay, and end-to-end assertions.

Adding a Protobuf field is normally binary wire-safe because old readers ignore unknown fields, but application code can still break on new enums or defaults. Never change an existing field number. Reserve a deleted field's number and name so neither is reused. During rolling releases, test old client with new server and new client with old server; same-version tests are insufficient.

When edge and internal clients need the same capability, share domain logic and an explicit contract source, then expose HTTP through JSON transcoding or a thin adapter where appropriate. The adapter must map HTTP status to gRPC status, headers to metadata, field names, authentication, and streaming limitations. Two manually synchronized business implementations will drift.

Step 5: Let production-shaped evidence decide migration

Use the real payload distribution and method mix, not a microbenchmark that serializes one tiny object. Against the same domain logic, measure end-to-end throughput, p50/p95/p99, client and server CPU, transferred bytes, connections, and memory. Cover unary calls, small and large messages, compression, server streams, slow consumers, and cross-zone traffic. Keep database and downstream work identical so the protocol effect can be isolated.

Run one low-risk internal method in dual-stack mode and canary by caller. Compare business results, error classification, deadline exceeded events, work that continued after cancellation, retry amplification, and trace completeness. Expand only after meeting written benefit and reliability thresholds. Keeping the HTTP API when the gain is insignificant is a valid result.

High-Quality Sample Answer

“I would not label the entire platform REST or gRPC. I would start with who calls each boundary, who controls upgrades, and the communication pattern.

For browsers, mobile apps, and partners, I would use REST-style HTTP with JSON. It works with the broad HTTP ecosystem, while OpenAPI supplies a machine-readable contract, SDK generation, and compatibility checks. The third-party webhook remains an HTTP POST because the sender's protocol is an external constraint. The ingress verifies the signature, deduplicates by event ID, persists the event, and processes it asynchronously.

The internal boundary has 20,000 calls per second and a server stream. I would benchmark production-shaped payloads. If all callers can use generated clients, the proxies, load balancers, and monitoring understand gRPC, and p99, CPU, or bandwidth improves enough to meet a written threshold, I would use gRPC there. Unary and streaming methods live in .proto, but each call still gets a deadline, cancellation stops spawned work, and only contractually safe operations are retried.

For evolution, the HTTP API uses OpenAPI diffs, old SDKs, and semantic replay to catch pagination or enum breaks. Protobuf field numbers never change, deleted fields are reserved, and old/new client-server pairs are cross-tested. If public and internal interfaces share a capability, one domain implementation serves an HTTP adapter and a gRPC service, or uses JSON transcoding after the mapping is verified.

I would canary by caller and watch end-to-end p99, CPU, bytes, status mapping, retry amplification, and trace completeness. If the improvement exists only in a microbenchmark while the production bottleneck remains the database, I would not expand the migration for protocol uniformity.”

Common Mistakes

  • Equating REST with HTTP/1.1 → HTTP semantics and transport versions are being confused → State that a REST API can run over HTTP/2 or HTTP/3.
  • Claiming gRPC is always faster → Storage, business logic, or proxies may dominate → Benchmark identical domain work with representative payloads and tail latency.
  • Saying REST has no strong contract → This ignores OpenAPI description, generation, and testing → Compare actual contract workflows, not a neglected specification with a maintained one.
  • Calling native gRPC directly from a browser → The browser lacks the required native gRPC control → Use gRPC-Web, JSON transcoding, or a BFF and account for limitations.
  • Omitting a gRPC deadline → A client can wait indefinitely and consume resources → Derive a deadline from the end-to-end budget and verify cancellation.
  • Treating Protobuf wire compatibility as application compatibility → New enums, defaults, and meanings can still break code → Cross-test versions and reserve field numbers.
  • Enabling retries at every layer → Failures amplify traffic and writes may repeat → Centralize retries and bound safe operations, remaining time, and attempts.
  • Keeping one protocol only for uniformity → Public integration cost or internal streaming needs are sacrificed → Use an explicit HTTP edge and gRPC internal boundary when justified.

Follow-Up Questions and Responses

Follow-up 1: Should an internal API with only 500 QPS still use gRPC?

QPS alone cannot decide. An organization with a mature gRPC platform, cross-language generated contracts, and a streaming requirement may still benefit at 500 QPS. A team with one simple CRUD service, mature HTTP tooling, and ample performance headroom probably has a lower operational cost with REST/HTTP+JSON. Write the target metric first; do not migrate if the gain cannot be demonstrated.

Follow-up 2: A public mobile app can use a generated gRPC client. Can gRPC be the public API?

It can be a candidate for controlled mobile clients, but test proxies, enterprise networks, debugging tools, certificate handling, version compatibility, and release cadence. Partners and browsers may still need an HTTP API, so public gRPC does not automatically remove dual-protocol cost. Decide by caller group instead of treating “public Internet” as one client.

Follow-up 3: How can one .proto expose a JSON API too?

Annotate methods with HTTP mappings and use JSON transcoding or a gateway. Before rollout, verify field names, null and default behavior, HTTP and gRPC status mapping, metadata and headers, authentication, caching, and streaming limitations. A .proto contract source reduces duplication, but the adapter semantics still require tests.

Follow-up 4: How does a gRPC stream resume without losing events?

The protocol provides streaming and ordering within one RPC, not a durable business subscription. Give events stable sequence numbers, retain a replayable log, and have the client persist or send its consumed cursor on reconnect. Define retention, expired-cursor handling, and deduplication. If those requirements dominate, compare a message log or queue instead of stretching an RPC into one.

Follow-up 5: A benchmark shows better gRPC p99, but production does not. What do you inspect?

Break latency into client queuing, DNS and connection work, proxies, serialization, application logic, database, and downstream calls. Check whether production payloads, compression, connection reuse, TLS, cross-zone routing, and hidden retries match the benchmark. If the protocol is a small fraction of total latency, optimize the dominant component instead of expanding the migration.

Public sources

Related questions