RPC — Remote Procedure Call
RPC is the idea that calling a function on a remote machine should look and feel like calling a function in your own process. The RPC framework hides marshalling, the network, and the protocol behind a generated stub that mimics a local function. It is the natural fit for service-to-service communication where both ends are owned by the same team and the API is action-oriented rather than resource-oriented.
How it works
RPC was first described by Birrell and Nelson in 1984. The premise: a remote call should look identical to a local call. You write result = server.getUser(42) and the framework figures out how to ship the argument 42 across the network, invoke the function on the server, and return the result. The programmer does not think about sockets, serialization, or protocols.
To make this work, RPC introduces four pieces:
- IDL (Interface Definition Language): a language-neutral declaration of the function signatures and types. Each side generates code from it.
- Stubs: on the client, a generated class that looks like the local function but actually serializes arguments and sends them. On the server, a generated skeleton that deserializes arguments and dispatches to the implementation.
- Marshalling: serializing the call's arguments into bytes (and the result back). Binary formats (Protobuf, Thrift, FlatBuffers) are typical; JSON-RPC uses JSON.
- Transport: usually TCP (or HTTP for compatibility). The framework manages the connection, retries, timeouts.
The programmer sees: a function. The framework does: IDL → code generation → serialization → transport → dispatch → serialization → return.
RPC vs REST is one of the most over-debated questions, and the answer is usually: it depends on what you are optimizing.
REST thinks in resources — nouns identified by URLs, acted on by HTTP verbs. REST is great for public APIs (third-party integrators want predictable URLs and HTTP semantics), for caching (HTTP caching works), and for evolvability (add fields, version URLs). REST is verbose (JSON over text), and it is awkward for operations that do not map cleanly to CRUD (/refundPayment? /users/42/transfer?).
RPC thinks in actions — verbs as function names, arguments as typed structures. RPC is great for internal service-to-service calls (typed, efficient, codegen-friendly), for operations that are not CRUD (ChargeCard, EnrichRecommendations, TranslateDocument), and for performance (binary protocols are 5-10x smaller than JSON). RPC is awkward when the consumer is unknown (typed stubs require code generation), for caching (no HTTP verbs to lean on), and for browser/mobile clients (gRPC's HTTP/2 framing is not natively fetchable from browsers without gRPC-Web).
The rule of thumb: public API to unknown clients → REST. Internal API to your own services → RPC. Most companies run both — REST at the edge, RPC (often gRPC) between internal services. GraphQL is a third option when you have many clients with different data needs.
RPC's original sin is what Waldo et al. pointed out in 1994: a local call and a remote call are not the same thing, and pretending they are leads to bugs. Local calls are fast, never fail (modulo crashes), and have predictable latency. Remote calls can take milliseconds or hours, can fail in partial ways (the server got your request but you never got the response), and can fail in surprising ways (network partition, server restart, timeout while the work is still running).
The eight fallacies of distributed computing (Peter Deutsch, Sun Microsystems):
- The network is reliable.
- Latency is zero.
- Bandwidth is infinite.
- The network is secure.
- Topology doesn't change.
- There is one administrator.
- Transport cost is zero.
- The network is homogeneous.
An RPC framework that pretends these are true (synchronous calls with no timeouts, no idempotency, no retries) will fail in production. The fixes that production RPC frameworks bake in:
- Timeouts: every call has a deadline; the framework aborts if the deadline passes.
- Retries with idempotency keys: retries are safe only if the operation is idempotent or carries an idempotency key.
- Circuit breakers: stop calling a failing service to let it recover.
- Backpressure: a slow server tells the client to slow down (gRPC trailers, Reactive Streams).
- At-most-once vs at-least-once semantics: documented per method, so callers know whether retries are safe.
The mature mental model: an RPC call is not 'a function that returns a value'. It is 'a request that may or may not arrive at the server, may or may not be processed, and may or may not return a response — and you must handle each case'. This is the difference between RPC as a protocol and RPC as a discipline.
When people say 'RPC' today in a microservices context, they usually mean gRPC: Google's framework using HTTP/2 and Protocol Buffers, with code generation for a dozen languages, streaming support, and built-in deadlines/cancelation. The next concept covers gRPC specifically. Other modern RPC frameworks include Apache Thrift (Facebook), Cap'n Proto (zero-copy), and Twirp (gRPC over HTTP/1.1 for browser compatibility).
Classic RPC is synchronous request-response, but modern systems often need more. Three patterns extend the model.
Async RPC (fire-and-forget): the caller sends a request and does not wait for a response. Used for events, notifications, telemetry — anything where the caller does not need the result. Often implemented via message queues (Kafka, NATS, RabbitMQ) rather than direct RPC, because the queue provides the durability and the natural decoupling. The RPC framing becomes 'publish to topic' rather than 'call function', but the conceptual model is the same.
Async request-reply: the caller sends a request, immediately gets a correlation ID, and polls or subscribes for the result. Used when a request takes seconds-to-minutes (video transcoding, report generation, ML inference). The RPC returns 202 Accepted with a job ID; the caller later GETs /jobs/{id} for the result, or subscribes to a webhook/SSE. This avoids holding a TCP connection open for the duration of the work and survives client disconnects.
Streaming RPC: the caller opens a long-lived call and the server (or client, or both) send a sequence of messages over it. gRPC's server-streaming, client-streaming, and bidirectional-streaming modes formalize this. Used for live data (logs, metrics, market data), large result sets (pagination over a stream instead of multiple round trips), and interactive sessions (chat, collaborative editing).
The point: 'RPC' is not just one pattern. It is a family of remote-invocation styles. The unifying property is that the application thinks in terms of typed procedure calls, not resources or documents. The wire protocol — sync, async, streaming, fire-and-forget — is the framework's problem, not the application's.
Why is treating a remote call like a local call dangerous, even when the RPC framework makes it syntactically identical?
Pick one answer.
Your team is building both a public API for third-party integrators and an internal service-to-service API. Which combination is most appropriate?
Pick one answer.
Which is NOT one of the eight fallacies of distributed computing?
Pick one answer.
Engineering mental model
Mental model. Think of RPC — Remote Procedure Call as a deliberate boundary in a system. The boundary exists because something becomes harder to manage when everything is done in one place: latency, scale, failure isolation, consistency, cost, or team ownership. The useful question is not “what does RPC — Remote Procedure Call mean?” but “what pressure makes this boundary worth introducing, and what new failure mode does it create?”
Before choosing RPC — Remote Procedure Call, name the workload, the critical user path, the dominant bottleneck, the failure you are trying to absorb, and the trade-off you are willing to accept. If you cannot name those five things, the design is probably premature.
curl -i https://api.example.com/v1/rpc
# Look for:
# - status code
# - latency
# - retryability
# - response sizeBack-of-the-envelope reasoning
Numerical lens: write down traffic, payload size, read/write ratio, peak multiplier and durability target before choosing a component. The numbers should justify the architecture.
Interactive thought experiment: RPC — Remote Procedure Call
Change the variables below and predict what breaks first in RPC — Remote Procedure Call. The production lab can later reuse these same inputs.
Change one variable at a time. Predict the failure mode first, then move the slider and see whether your mental model matches the simplified system response.
If you are stuck on RPC — Remote Procedure Call, start by drawing the request path and marking every network hop, stateful component, queue, cache and failure boundary. Then estimate where the system will saturate.
You increase traffic by 10× in a system using RPC — Remote Procedure Call. What should you inspect first?
Pick one answer.
Which statement is the safest engineering habit when using RPC — Remote Procedure Call?
Pick one answer.
You have dashboards for traffic, latency, errors and saturation. You can change the architecture, but every change has operational cost.
Production scenario: your system uses RPC — Remote Procedure Call, traffic suddenly spikes, and p99 latency doubles. What is your first move?
Interview drill
Answer this without notes: When would you choose RPC — Remote Procedure Call, and when would you intentionally avoid it? Mention at least one bottleneck it addresses, one failure mode it introduces, and one alternative. Then quantify the workload you are designing for.
A useful engineering lens for RPC - Remote Procedure Call: define the problem it solves, the simpler design that fails first, the constraint that forces you to introduce this concept, and the new failure modes the concept creates.
Numerical sanity check
Back-of-the-envelope reasoning beats fake precision. State your traffic, payload, concurrency and growth assumptions explicitly, then calculate enough to know whether the current architecture is orders of magnitude away from the target.
Do not optimize for a memorized definition. Reason from the workload and failure mode.
Imagine the simplest version of a system using RPC - Remote Procedure Call. What breaks first as traffic grows by 10×, and what would you change before reaching 100×?
Pick one answer.
What you gain, what you pay
- +Typed, code-generated stubs catch interface errors at compile time.
- +Binary protocols (Protobuf, Thrift) are 5-10x smaller and faster than JSON.
- +Action-natural for operations that are not CRUD (ChargeCard, TranslateDocument).
- +Streaming support (in modern frameworks like gRPC) for bidirectional data flow.
- −Hides network failure behind local-looking syntax — a known footgun.
- −Requires code generation and an IDL toolchain — operational complexity.
- −Hard to consume from browsers without gRPC-Web or similar proxies.
- −Less cacheable than REST (no HTTP verb semantics, no URL-keyed caching).
- −Schema evolution requires IDL discipline (field numbers, optional/required, deprecated).
How this breaks in production
- Treating remote calls like local calls — no timeouts, no retries, no idempotency.
- Synchronous call chains: A → B → C, where C's slowness stalls B stalls A. Cascade failure.
- Infinite retries without backoff — take down the already-struggling server.
- Non-idempotent operations retried on timeout — duplicate side effects (double charges).
- IDL drift between client and server — silent corruption when fields do not match.
Don't fall into these traps
- •Defaulting to no timeout or a 30s timeout — deadlines should be tight and propagated.
- •Forgetting idempotency keys on mutations — retries cause double execution.
- •Chaining synchronous RPCs without a circuit breaker — cascade failures take down services.
- •Not versioning the IDL — a breaking change rolls out as a deployed bug.
- •Using RPC for a public API to unknown clients — forces them to use your IDL toolchain.
- •Ignoring backpressure — a slow server gets faster clients hammering it.
Real systems using this
How real systems implement this
- Google internal services — Nearly all internal Google service-to-service traffic uses Stubby (gRPC's predecessor) — typed, binary, deadline-aware. Billions of calls per second across tens of thousands of services.
- Netflix — Uses gRPC for many internal microservice calls after migrating from REST/HTTP. The typed contracts and streaming support were the main drivers.
Practice saying it out loud
- Q1What is the difference between RPC and REST? When would you choose each?
- Q2Explain why 'treating a remote call like a local call' is dangerous. What are the failure modes?
- Q3Name three of the eight fallacies of distributed computing. How does each one bite you in production?
- Q4Your internal service calls another service that is suddenly slow. What patterns do you apply?
- Q5Why do modern microservices often use gRPC internally and REST externally?
Further reading & references
Core explanations are original NO CAP material. External references are provided for deeper study and standards.
What next?
Mark as understood once the mental model clicks.
Next recommended
gRPC — Google's RPC Framework