← Computer Networks
Distributed & Cloud Networking

Microservices Communication — REST vs gRPC

Watch: "This Is Why gRPC Was Invented" — Hussein Nasser

Once a system splits into services, every internal call becomes a network call, and the protocol choice compounds across hundreds of services. Interviewers use "REST or gRPC internally?" to check whether you understand the latency, tooling, and typing trade-offs — not just that gRPC is "faster."

What Is REST?

REST is a way for two programs to talk over the internet using plain, readable messages — the same basic technology your browser uses to load a web page. A request asks for something like "get user 123," and the response comes back as ordinary, human-readable text (JSON) that's easy for any programmer, tool, or browser to read and debug.

What Is gRPC?

gRPC is a faster, more compact way for two programs to talk, designed for services that call each other constantly behind the scenes rather than for a human eyeballing the traffic. Both sides agree on a strict contract in advance for exactly what a request and response look like, then send that data packed down into dense binary instead of readable text — smaller and quicker to process, at the cost of no longer being human-readable.

Where This Is Used

A Day-to-Day Example

REST is like ordering at a restaurant counter in plain spoken language — "I'd like a medium coffee, oat milk" — anyone working the counter can understand it immediately, even a new hire, but it takes longer to say than it needs to. gRPC is like a kitchen's internal ticket system, where the same order becomes a short pre-agreed code that only kitchen staff who know the code can read — faster to process, but useless to someone who wasn't trained on that code.

Deep Dive: What Actually Goes On the Wire

A REST call like GET /users/123 over HTTP/1.1 sends human-readable text — headers, then a JSON body — and, in the common case, opens a new TCP connection (or reuses one via keep-alive) per logical request stream. A gRPC call is a typed method invocation, GetUser(userId), serialized to compact binary Protocol Buffers and sent as an HTTP/2 stream, where many concurrent calls share one underlying TCP connection.

Payload size of the same object in JSON and ProtobufREST + JSON{"id":123,"name":"Ada Lovelace","email":…,"active":true}≈180 BgRPC + Protobuftags 1–4 + values≈40–60 BJSON repeats field names in every message; Protobuf sends numbered tags,and both sides get the names from the shared .proto schema
The same user object drawn to scale: roughly a third of the size in Protobuf, and cheaper to parse. The trade-off is readability: JSON can be read in any log or browser, Protobuf needs the schema. The full examples are below.
JSONProtobuf
REST/JSON over HTTP/1.1
  GET /users/123 HTTP/1.1
  Host: api.example.com
  Authorization: Bearer ...

  Response body (≈180 bytes for a small object):
  {"id":123,"name":"Ada Lovelace","email":"[email protected]","active":true}

gRPC/Protobuf over HTTP/2
  .proto contract:
    service UserService {
      rpc GetUser(GetUserRequest) returns (User);
    }
    message User { int64 id = 1; string name = 2; string email = 3; bool active = 4; }

  Wire bytes (≈40-60 bytes for the same object):
    field 1 (id):     varint  123
    field 2 (name):   len-prefixed  "Ada Lovelace"
    field 3 (email):  len-prefixed  "[email protected]"
    field 4 (active): varint  1
  — field names never travel on the wire, only numbered tags; the schema
    (the .proto file) supplies the names on both ends.

That difference — field names in every JSON response vs. numbered tags resolved against a shared, compiled schema — is most of where gRPC's 4-10x size reduction comes from, on top of HTTP/2's binary framing and multiplexing (many logical streams over one TCP connection, no head-of-line blocking at the application layer the way multiple HTTP/1.1 connections can have at the TCP layer).

Deep Dive: gRPC's Four Call Shapes

REST is fundamentally one shape — request in, response out. gRPC, because it's built on HTTP/2 streams instead of one-shot request/response, supports four:

This is the concrete reason "gRPC supports streaming" matters beyond a buzzword: it maps directly onto real use cases — service meshes streaming config updates, telemetry pipelines batching metrics, or any workload where request/response is too coarse-grained.

Key Pointers

How It Works

  1. Decide the audience: external/public clients and browsers favor REST+JSON for accessibility and cacheability; internal service-to-service calls favor gRPC for speed and typed contracts.
  2. For gRPC, define the contract in a .proto file — services, methods, and message schemas — and generate client/server stubs for each language.
  3. The gRPC client serializes the request to protobuf and sends it over an HTTP/2 stream; the server deserializes, executes, and streams the response back the same way.
  4. For REST, the client serializes to JSON, sends a standard HTTP verb/URL request, and the server responds with a JSON body and status code.
  5. Version both contracts deliberately — protobuf's field numbers make additive schema evolution safe (new optional fields, never reusing a retired field number); REST needs explicit URL/header versioning.

Try It Yourself

Compare the two side by side against a real service. For REST, plain curl is enough:

# REST — inspect headers and body size directly
curl -v https://api.example.com/users/123

# gRPC — first, ask the server what services/methods it exposes
# (requires the server to support gRPC reflection)
grpcurl -plaintext localhost:50051 list

# Describe a specific method's request/response schema
grpcurl -plaintext localhost:50051 describe user.UserService.GetUser

# Actually invoke it, passing the request as JSON (grpcurl handles
# the JSON -> protobuf translation for you)
grpcurl -plaintext -d '{"userId": 123}' \
  localhost:50051 user.UserService.GetUser

Compare the response times and payload sizes reported by each — on a real internal call this is where you'll see gRPC's binary framing and HTTP/2 multiplexing pay off, especially under concurrent load.

Interview Q&A

Q: Should every microservice use gRPC?

A: No. Choose based on who calls the API: public clients and browsers need REST/JSON's ubiquity, cacheability, and debuggability with tools everyone already has (curl, browser devtools). Internal service-to-service calls where you control both ends benefit from gRPC's typed contracts and performance. Also weigh ecosystem interoperability (does a partner's system only speak REST?), whether streaming actually matters for this call, latency sensitivity, and the operational cost — gRPC needs more tooling for browser clients (grpc-web plus a proxy) and for load balancers that weren't built with HTTP/2 streams in mind.

Q: Why is gRPC faster than REST/JSON in practice?

A: Two independent factors compound: the wire format (binary protobuf with numbered fields vs. text JSON with field names repeated every time) is smaller and faster to (de)serialize, and the transport (HTTP/2 multiplexed streams on one connection vs. HTTP/1.1's per-connection request queuing) removes a source of head-of-line blocking. Neither alone explains all of the gap — it's both together.

Q: How do you handle breaking changes to a gRPC API without downtime?

A: Prefer additive, backward-compatible changes — add new fields with new field numbers, never reuse or renumber a retired field, and treat every field as optional so old and new clients can both parse messages from the other version. For a genuine breaking change, version the service itself (e.g. a new UserServiceV2) and run both during a migration window, the same way you'd version a REST endpoint.

Q: Can a browser call a gRPC service directly?

A: Not standard gRPC — browsers can't set the raw HTTP/2 trailers gRPC needs. You need grpc-web, a JS client library that speaks a modified protocol, plus a proxy (e.g. Envoy) in front of your gRPC servers that translates grpc-web calls into real gRPC. This extra hop is exactly why public-facing APIs usually stay REST (or GraphQL) and gRPC stays behind the edge.

System Design Tie-in

In a prompt like "design the backend for a food-delivery platform," the order service, the driver-matching service, the pricing service, and the restaurant service all talk to each other internally, at high volume, under latency pressure (a driver-matching decision has to happen in milliseconds). That's the textbook case for gRPC between services — typed contracts prevent an order-service deploy from silently breaking pricing-service's parsing, and the binary framing keeps p99 latency low under heavy fan-out. The public mobile app and web client, meanwhile, talk to an API gateway over REST (or GraphQL) — the gateway is where gRPC-internal, REST-external gets stitched together, and calling that out explicitly is what separates a strong answer from one that just says "gRPC everywhere."

Trade-offs

gRPC's typed contracts catch integration bugs at compile time instead of runtime, and its binary framing plus HTTP/2 multiplexing meaningfully cuts p99 latency at high internal call volume — this is why most large-scale backends (and many model-serving stacks) use gRPC between services. REST remains better for anything public-facing: it's cacheable by generic HTTP infrastructure, debuggable with curl, and doesn't require clients to have your .proto files. A common interview trap is treating this as purely binary — real systems typically run gRPC internally and expose a REST (or GraphQL) gateway at the edge that translates for external clients.