Q24 — Why does gRPC exist — what does REST get wrong at scale?
What is the context where REST starts showing cracks?
REST over HTTP with JSON works exceptionally well for public APIs, browser-based clients, and human-readable data exchange. It's simple, universally supported, and easy to debug with any HTTP tool.
But consider the internal communication layer of a large system — hundreds of microservices calling each other thousands of times per second, across language boundaries (some services in Go, some in Java, some in Python), with complex data structures and strict contracts. Here, JSON over HTTP/1.1 starts accumulating costs that individually seem small but together become significant.
gRPC was built by Google to solve these costs. Google runs thousands of internal services making billions of RPCs (Remote Procedure Calls) per day. At that scale, inefficiencies in serialization format, wire protocol, and developer workflow are worth eliminating. gRPC is the open-source version of what Google built internally (called Stubby).
What does JSON cost you at scale?
JSON is a text format. Text formats are designed to be human-readable, and that readability has costs.
Serialization cost. Converting a Go struct or a Java object into a JSON string requires reflecting over every field, formatting numbers as text characters, escaping strings, and producing a UTF-8 byte sequence. Deserializing JSON means parsing that text character by character, interpreting types from context (is "42" a string or a number that got quoted by mistake?), and allocating new objects for nested structures. This CPU work is paid on every single RPC call, on both sides.
Wire size. JSON is verbose. Field names are repeated in every response — if you return a list of 1000 users, the string "email" appears 1000 times in the response. A field like created_at costs 10 bytes just for the key, plus the ISO timestamp value. Numbers are stored as their digit characters: the number 1234567890 is 10 bytes as text but only 4 bytes as a 32-bit integer. At millions of messages per second, this size difference is real network bandwidth and real memory allocation.
No schema enforcement. JSON has no built-in type system. A field named amount could be a float, an integer, or a string depending on which team wrote the service and on which day. The only way to know is documentation, which goes stale. Mismatches in field names or types between services cause runtime errors that only surface when the specific code path is exercised — not at compile time or deploy time.
What does HTTP/1.1 cost you at scale?
Every HTTP/1.1 request between services is subject to the same overhead that made browser page loads slow. Headers — Host, Content-Type, Authorization, and others — are sent as uncompressed text on every request. For a microservice making 50,000 RPC calls per second, those redundant headers are non-trivial bandwidth.
HTTP/1.1's sequential request model within a single connection means you can't pipeline many concurrent calls efficiently without maintaining many parallel connections per service-to-service pair — each connection with its own TCP and TLS overhead.
What does gRPC use instead?
gRPC makes two fundamental substitutions: Protocol Buffers (protobuf) instead of JSON, and HTTP/2 instead of HTTP/1.1.
Protocol Buffers — a binary schema-first format
Protobuf is not JSON. It's a binary serialization format where the schema is defined in a .proto file:
message User {
int64 id = 1;
string email = 2;
string name = 3;
int64 created_at = 4;
}
The key differences from JSON:
Binary encoding: The number 1234567890 is encoded as 5 bytes in protobuf (variable-length encoding), not 10 bytes of text. An integer field costs 1–10 bytes regardless of value. Strings are length-prefixed binary sequences. Field names are not transmitted at all — each field has an integer tag number (id = 1, email = 2), and only the tag number appears in the encoded bytes. This makes protobuf messages typically 3–10x smaller than equivalent JSON.
Faster serialization: Binary formats skip all the text parsing overhead. Decoding a protobuf message is fast typed memory operations, not character-by-character parsing with type inference.
Schema enforcement: The .proto file is the source of truth. gRPC generates client and server code in your language from the .proto. If you add a required field and don't update both sides, the mismatch is caught at code generation time — before deployment. Type safety is enforced by the compiler, not discovered at runtime in production.
Backward and forward compatibility: Protobuf fields are identified by tag numbers, not names. Adding a new field with a new tag number doesn't break old code that doesn't know about it — old code just ignores unknown tags. Removing a field (by tagging it as reserved) doesn't break new code reading old messages — the missing field gets a zero value. This makes evolving APIs across services that deploy at different times dramatically safer.
HTTP/2 — a fundamentally better wire protocol
gRPC runs exclusively on HTTP/2 (covered in depth in Q03). The relevant benefits for service-to-service communication:
Multiplexing: Multiple concurrent RPCs flow over a single connection. No need to maintain a pool of parallel connections per service pair. The overhead of connection management is paid once.
Header compression: HTTP/2's HPACK compression means repetitive headers (which dominate in service-to-service traffic) are sent as small integer references after the first occurrence. Header overhead drops dramatically.
Built-in streaming: HTTP/2's framing model makes streaming first-class. gRPC exposes this as four call types.
What are gRPC's four call types?
This is one of gRPC's most powerful features over REST: streaming is a native concept, not a hack.
Unary RPC: One request, one response. The standard RPC. Equivalent to a REST POST request.
client → request → server → response → client
Server streaming: One request, a stream of responses. The server sends multiple messages and closes the stream when done. Useful for: returning large result sets progressively, sending live updates (stock prices, log tailing), pushing a file in chunks.
client → request → server → [response, response, response...] → client
Client streaming: A stream of requests, one response. The client sends many messages; the server processes them all and replies once. Useful for: batch uploads, sensor data ingestion, chunked file upload.
client → [request, request, request...] → server → response → client
Bidirectional streaming: Both sides send streams of messages concurrently. The protocol handles interleaving. Useful for: real-time chat, collaborative editing, game state sync between client and server.
client → [request...] → server
client ← [response...] ← server
(simultaneously, in both directions)
REST over HTTP/1.1 has no equivalent to client streaming or bidirectional streaming. You can approximate server streaming with SSE (covered in Q06), but client streaming and bidirectional require WebSockets — a separate protocol with different semantics.
What are gRPC's limitations?
gRPC is not a replacement for REST everywhere.
Browser support is limited. Browsers cannot make native gRPC calls because browsers don't expose HTTP/2 framing at the API level. To use gRPC from a browser, you need gRPC-Web — a translation layer that wraps gRPC calls in standard HTTP/1.1, with a proxy (Envoy, grpc-web-proxy) in between. This works but adds complexity. For public APIs consumed by browsers, REST is still the right choice.
Debugging is harder. JSON is readable with any text editor or browser dev tools. A protobuf message is binary — curling a gRPC endpoint and reading the response is not possible without a protobuf schema and a decoding tool. Tooling has improved (grpcurl, Postman's gRPC support), but the debug experience is meaningfully worse than REST + JSON.
Not human-readable. For APIs designed for third-party developers who write manual HTTP clients, JSON's readability is a feature. Binary protobuf requires tooling and schema files — a higher barrier to entry for external consumers.
The mental model
REST + JSON + HTTP/1.1:
├── JSON: text, verbose, slow to parse, no schema enforcement
├── HTTP/1.1: uncompressed headers, sequential within connection
└── Great for: public APIs, browsers, human readability
gRPC = protobuf + HTTP/2:
├── Protobuf:
│ Binary → 3–10x smaller than JSON
│ Faster serialize/deserialize
│ Schema in .proto → type-safe code generated at build time
│ Field tags → backward/forward compatible
├── HTTP/2:
│ Multiplexed streams → many RPCs on one connection
│ Header compression → low overhead
│ Streaming: unary / server-streaming / client-streaming / bidirectional
└── Great for: service-to-service (internal), high throughput, streaming, polyglot systems
gRPC limitations:
├── No native browser support (need gRPC-Web proxy)
├── Binary = harder to debug
└── Higher barrier for external API consumers
Choose REST: public APIs, browser clients, simplicity matters
Choose gRPC: internal microservices, high RPC volume, strong typing needed, streaming required
gRPC exists because REST's design decisions — optimized for human readability and broad interoperability — become costs rather than benefits when the consumer is not a human but another service making millions of calls per second. gRPC trades readability for efficiency, and at scale, that trade is worth it.