REST vs. RPC/gRPC

Topics Covered

REST APIs

Resources and URLs

HTTP Methods as Intent

Statelessness and Scalability

Caching Built Into the Protocol

Where REST Struggles

gRPC (Remote Procedure Call)

How gRPC Works

Why Binary Beats JSON for Internal Traffic

HTTP/2 and Streaming

When to Use gRPC

When NOT to Use gRPC

Schema Evolution and Backward Compatibility

REST is not a protocol — it is a set of constraints layered on top of HTTP. The reason it dominates public APIs is that it reuses everything the web already provides: URLs for addressing, HTTP methods for intent, status codes for outcomes, headers for metadata, and caching for performance. You do not need a special client library or code generator. A curl command, a browser, or any HTTP client works immediately.

The core idea is simple: treat your data as resources and give each one a stable URL.

REST Request Response Flow

Resources and URLs

Every entity in your system gets a predictable address. Collections live at /api/users. Individual items live at /api/users/123. Nested resources follow the same pattern: /api/users/123/orders. This uniformity means a developer who has seen one endpoint in your API can guess the shape of every other endpoint without reading documentation.

HTTP Methods as Intent

REST maps operations to HTTP methods:

 
1GET    /api/users/123Read user 123
2POST   /api/users        → Create a new user
3PUT    /api/users/123Replace user 123 entirely
4PATCH  /api/users/123Update specific fields on user 123
5DELETE /api/users/123Remove user 123

The distinction between PUT and PATCH matters for idempotency. PUT replaces the entire resource — sending the same PUT twice produces the same result. PATCH applies a partial update, and depending on implementation, may not be idempotent. GET and DELETE are also idempotent. POST is not — sending the same POST twice creates two resources.

Interview Tip

In interviews, when asked about API design, default to REST unless you have a specific reason not to. REST's alignment with HTTP means load balancers, CDNs, API gateways, and monitoring tools all understand your traffic without configuration. That infrastructure compatibility is worth more than raw performance in most systems.

Statelessness and Scalability

Every REST request carries everything the server needs: authentication token, content type, accepted response format. The server stores no session state between requests. This is why REST scales horizontally — any server instance can handle any request. A load balancer can route traffic round-robin without sticky sessions. If a server crashes mid-conversation, the client simply retries against another instance with no context loss.

 
GET /api/users/123
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
Accept: application/json

Caching Built Into the Protocol

REST leverages HTTP caching natively. The server sets Cache-Control and ETag headers. Intermediaries (CDNs, reverse proxies) cache responses automatically. Conditional requests with If-None-Match avoid transferring unchanged data — the server returns 304 Not Modified with no body.

For read-heavy APIs (product catalogs, user profiles, configuration endpoints), this means 90%+ of traffic never reaches your application servers. The cache absorbs it. This is free performance that gRPC cannot match without building a custom caching layer.

Where REST Struggles

REST's resource-oriented model breaks down in three scenarios:

Complex operations — "Transfer $100 from account A to account B" does not map cleanly to a single resource. You end up with awkward endpoints like POST /api/transfers that feel like RPC calls wearing REST clothing.

Real-time data — REST is request-response. The client must poll for updates. WebSockets or Server-Sent Events solve this, but they step outside the REST model entirely.

Chatty microservices — When Service A calls Service B 50 times per request, the overhead of JSON serialization, HTTP/1.1 connection management, and text parsing adds up. Each call pays the cost of encoding human-readable JSON when no human will ever read it.

This last point is exactly where gRPC enters the picture.

gRPC exists because REST's strengths become weaknesses inside a datacenter. When Service A calls Service B 200 times per second, you do not need human-readable JSON, browser compatibility, or URL-based routing. You need speed, type safety, and streaming. gRPC delivers all three by combining Protocol Buffers (binary serialization), HTTP/2 (multiplexed connections), and code generation (typed client stubs).

gRPC Service Communication

How gRPC Works

You define your API in a .proto file — the service methods, request types, and response types. The Protobuf compiler (protoc) generates client stubs and server interfaces in your language. Your client code calls a method on the stub as if it were a local function. Under the hood, the stub serializes the request to compact binary, sends it over a persistent HTTP/2 connection, and the server deserializes and executes the handler.

proto
1service OrderService {
2  rpc CreateOrder(OrderRequest) returns (OrderResponse);
3  rpc ListOrders(ListOrdersRequest) returns (stream Order);
4}
5
6message OrderRequest {
7  string customer_id = 1;
8  string item_id = 2;
9  int32 quantity = 3;
10}

The .proto file is the single source of truth. Both client and server generate code from it, so type mismatches are caught at compile time rather than as runtime 400 errors. Change a field type from string to int32 and the compiler tells you every callsite that breaks — before you deploy.

Why Binary Beats JSON for Internal Traffic

JSON is text. Every integer is a string of decimal digits. Every field name is repeated in every message. A JSON payload for an order might be 500 bytes. The same data in Protobuf is 80 bytes — field names replaced by small integer tags, integers stored in variable-length encoding, no delimiters or whitespace.

At 10,000 requests per second between two services, that difference is 4 MB/sec versus 0.8 MB/sec. Multiply by 50 service-to-service communication paths and the bandwidth savings compound. Serialization and deserialization are also faster — Protobuf parsing is 5-10x faster than JSON parsing because there is no string scanning, no escape handling, no unicode normalization.

Key Insight

The performance advantage of gRPC over REST is not about the transport (HTTP/2 vs HTTP/1.1) — it is about serialization. Protobuf's binary encoding is 5-10x smaller and 5-10x faster to parse than JSON. HTTP/2 multiplexing helps with connection efficiency, but the serialization difference is where most of the latency savings come from in practice.

HTTP/2 and Streaming

gRPC uses HTTP/2, which multiplexes many requests over a single TCP connection. No head-of-line blocking between requests. No connection pool management. One connection per service pair handles thousands of concurrent RPCs.

But HTTP/2's bigger contribution to gRPC is streaming. Four patterns cover every communication need:

Unary — One request, one response. The standard RPC call, equivalent to a REST request-response.

proto
rpc GetOrder(GetOrderRequest) returns (Order);

Server streaming — Client sends one request, server returns a stream of responses. Use for live feeds, paginated results sent incrementally, or monitoring data.

proto
rpc ListOrders(ListOrdersRequest) returns (stream Order);

Client streaming — Client sends a stream of messages, server responds once after receiving all of them. Use for file uploads, batch writes, or sensor data ingestion.

proto
rpc UploadMetrics(stream Metric) returns (UploadSummary);

Bidirectional streaming — Both sides send streams concurrently. Use for chat, multiplayer games, or collaborative editing where data flows both ways continuously.

proto
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
gRPC Streaming Patterns

When to Use gRPC

gRPC wins in three scenarios:

Internal microservice communication — Services within your datacenter call each other frequently. The overhead of JSON serialization and HTTP/1.1 connection management adds latency that compounds across service chains. A request that traverses 5 services saves 20-50ms total by switching from REST to gRPC.

Real-time streaming — gRPC's native streaming replaces the need for WebSockets or Server-Sent Events. The bidirectional streaming pattern handles chat, live dashboards, and event-driven architectures naturally.

Polyglot environments — One .proto file generates clients and servers in Go, Java, Python, Rust, C++, and more. The contract is language-agnostic. A Go service can call a Python service with full type safety, no hand-written HTTP client needed.

When NOT to Use gRPC

Public APIs — Third-party developers expect REST. They want to test your API with curl or Postman, read JSON responses, and integrate without installing code generators. gRPC requires a .proto file, a code generator, and a gRPC-aware client library. The onboarding friction is too high for public consumption.

Browser clients — Browsers cannot make native gRPC calls. The workaround (gRPC-Web) adds a proxy layer that converts between browser HTTP and gRPC. This works but adds latency and operational complexity. If your primary client is a browser, REST or GraphQL is simpler.

Common Pitfall

A common mistake is adopting gRPC for all communication because it is faster. The performance gain only matters at scale — if your services exchange 10 requests per second, the difference between JSON and Protobuf is microseconds. The cost you pay is tooling complexity, debugging difficulty (binary payloads are not human-readable), and a steeper learning curve for new engineers. Choose gRPC when the performance gap is measurable, not theoretical.

Schema Evolution and Backward Compatibility

Protobuf handles schema changes gracefully through field numbering. Each field has a numeric tag (1, 2, 3...) that is what actually gets encoded in the binary — not the field name. This means:

  • Adding a field is safe. Old clients ignore unknown tags. New clients see the new field with its default value when talking to old servers.
  • Removing a field requires marking the tag as reserved so it is never reused. Old clients sending the removed field cause no harm — the server ignores it.
  • Renaming a field is free. Only the tag number matters in the binary encoding.
  • Changing a field type is breaking. A client expecting string that receives int32 will fail.

This is more robust than JSON, where field names are the identifiers and any rename is a breaking change for every client that parses by name.