Skip to content

How RPC Works

TL;DR

RPC (Remote Procedure Call) makes calling a function on a remote server feel like calling a local function. While REST thinks in resources (nouns like /events/123), RPC thinks in actions (verbs like getEvent('123')). Under the hood, RPC uses stubs, serialization, and network transport to hide the complexity of distributed communication. It fell out of favor in the early web era but roared back with the rise of microservices.

The Phone Call vs. the Letter

Here's the simplest way to understand the difference between RPC and REST.

REST is like sending a letter. You address it to a specific mailbox (/events/123), you specify what you want done with it (GET, PUT, DELETE), and you wait for a response. The postal system (HTTP) handles delivery. Everything is organized around where things live — the mailboxes.

RPC is like calling someone on the phone. You dial a number, tell them exactly what you need — "Hey, get me the details for event 123" — and they do it and tell you the result. You don't care where their filing cabinet is. You just called a function and got an answer.

That's the core mental model: REST is resource-oriented, organized around nouns. RPC is action-oriented, organized around verbs — function calls.

Neither is "better." They're different ways of thinking about the same problem, and modern systems often use both.

RPC call flow

Action-Oriented vs. Resource-Oriented Thinking

Let's make this concrete. Imagine you're building a Ticketmaster-like platform. Here's what the same operations look like in both paradigms:

Operation REST Approach RPC Approach
Get event details GET /events/123 getEvent('123')
Create a booking POST /events/123/bookings createBooking(eventId='123', userId='789')
List available tickets GET /tickets?event_id=123&status=available getAvailableTickets(eventId='123')
Cancel a booking DELETE /bookings/456 cancelBooking(bookingId='456')
Check if user has access GET /users/789/permissions?resource=event_123 checkPermission(userId='789', resource='event_123')

Notice how the REST column reads like navigating a filing system — you're always targeting a thing at a specific location. The RPC column reads like calling functions in your own codebase — you're telling a remote server to do something and passing arguments.

Both work. But look at that last row — checkPermission(). In REST, you have to awkwardly model "permission checking" as a resource. In RPC, it's just... a function call. Some actions don't map cleanly to resources, and that's where RPC shines.

How RPC Works Under the Hood

When you write getEvent('123') in your code, it feels like a local function call. But a lot is happening behind the scenes to make that illusion work. Here's the step-by-step flow:

Client Code                          Server Code
     |                                    |
     | 1. Call getEvent('123')            |
     |        |                           |
     |  [Client Stub]                     |
     |   2. Serialize args               |
     |   3. Send over network  -------->  |
     |                              [Server Stub]
     |                               4. Deserialize args
     |                               5. Call real getEvent('123')
     |                               6. Get result
     |                               7. Serialize response
     |                          <--------  8. Send over network
     |  [Client Stub]                     |
     |   9. Deserialize response          |
     |  10. Return result                 |
     |                                    |

Here's what each piece does:

1. The Client Stub (Proxy) — This is auto-generated code that lives on the client side. When your code calls getEvent('123'), it's actually calling the stub, not the real function. The stub's job is to pretend to be the real function while handling all the network plumbing.

2. Serialization — The stub takes your function arguments ('123') and converts them into a format that can be sent over the network. This could be binary (like Protocol Buffers), JSON, XML, or any agreed-upon format.

3. Network Transport — The serialized data is sent over the network to the server. This typically happens over TCP, and in modern systems like gRPC, over HTTP/2.

4-5. Server Stub (Skeleton) — On the server side, another piece of auto-generated code receives the request, deserializes the arguments, and calls the real getEvent() function that actually talks to the database.

6-8. Response — The real function returns a result. The server stub serializes it and sends it back over the network.

9-10. Client Gets the Result — The client stub deserializes the response and returns it to your code as if the function had run locally.

The magic of RPC is that steps 2-9 are completely invisible to the developer. You write result = getEvent('123') and you get back an event object. The fact that it traveled across a network, got serialized and deserialized, and was executed on a completely different machine is hidden behind the stub.

Interview Tip

If an interviewer asks "how does RPC work," walk through this stub-serialize-transport-deserialize-execute flow. It shows you understand that RPC isn't magic — it's a well-defined mechanism with clear performance implications (serialization cost, network latency, failure modes).

A Brief History: RPC's Wild Ride

RPC isn't new. It's one of the oldest ideas in distributed computing, and its history explains why it keeps coming back:

Era Technology What Happened
1984 Sun RPC / ONC RPC One of the first RPC frameworks. Powered NFS (Network File System). Simple but limited to C.
1990s CORBA The "grand unified" RPC system. Promised language-agnostic communication. Became horrifically complex.
Late 1990s XML-RPC RPC over HTTP with XML payloads. Simple, but verbose.
2000s SOAP XML-RPC's enterprise successor. Massively complex WSDLs and schemas. Became a symbol of over-engineering.
2000s REST takes over Roy Fielding's thesis. Simple, HTTP-native, human-readable. REST won the public API war.
2015 gRPC (Google) Modern RPC with Protocol Buffers + HTTP/2. Fast, type-safe, streaming. The microservices era brought RPC back.

Why RPC Fell Out of Favor (And Why It Came Back)

In the early 2000s, SOAP and CORBA gave RPC a bad reputation. They were complex, brittle, and required massive XML schemas just to say "hello." When REST came along with its simple URLs and JSON, it felt like a breath of fresh air. The industry collectively said "never again" to RPC.

So what changed?

Microservices happened. When you have a monolith, you don't need RPC — your functions are all in the same process. But when you break that monolith into 50 or 500 microservices, those services need to talk to each other. A lot. With tight performance requirements.

REST works great for public-facing APIs where human readability matters. But for internal service-to-service communication where:

  • Both sides are machines, not humans
  • You control both the client and the server
  • You need maximum performance
  • You have dozens of languages across services
  • You need strict type safety across team boundaries

...RPC starts looking really good again. Modern RPC frameworks like gRPC fixed the complexity problems that plagued CORBA and SOAP, keeping the core idea (call remote functions like local ones) while adding type safety, performance, and simplicity.

REST vs. RPC: The Mental Model Comparison

Here's a comprehensive comparison to cement the difference:

Aspect REST RPC
Mental model "Interact with resources" "Call remote functions"
URL design Nouns: /events/123 Verbs: getEvent()
Data format Usually JSON (text) Varies: JSON, XML, binary (Protobuf)
Coupling Loose — client discovers resources Tighter — client must know function signatures
Best for Public APIs, CRUD operations Internal APIs, complex actions
Human readability High — you can test with curl Lower — binary formats need tooling
Browser support Native Requires special tooling
Caching Built-in HTTP caching (GET requests) Not built-in — must implement yourself
Error handling HTTP status codes (404, 500, etc.) Framework-specific error codes
Discoverability URLs are self-describing Need documentation or .proto files

When Actions Don't Map to Resources

REST's Achilles' heel is actions that don't naturally fit into the CRUD-on-resources model. Consider these operations:

# These are awkward in REST:
checkPermission(userId, resource)    →  GET /permissions?user=123&resource=event_456 (forced)
validateToken(token)                 →  POST /token-validations (contrived)
sendNotification(userId, message)    →  POST /notifications (works but hides intent)
calculateShippingCost(items, dest)   →  POST /shipping-calculations (feels wrong)
runHealthCheck()                     →  GET /health (this one actually works well)

Some of these can be shoe-horned into REST by "nouning the verb" — turning sendNotification into POST /notifications. But notice how the REST version sometimes obscures what's actually happening. POST /token-validations doesn't scream "I'm checking if a JWT is valid" the way validateToken() does.

In RPC, these are just function calls. No mental gymnastics needed. This is a key reason why internal service-to-service communication often uses RPC — the services are performing complex actions on each other's behalf, and modeling every action as a resource adds cognitive overhead without adding value.

The pragmatic approach that most large-scale systems use? REST for public-facing APIs (where discoverability and human readability matter) and RPC for internal communication (where performance and type safety matter). You'll see this pattern at Google, Netflix, Uber, and most companies operating at scale.

Interview Tip

When designing a system in an interview, you typically outline the user-facing REST API during the "API Design" step. You don't need to formally design internal RPC interfaces. Instead, during the high-level design step, you can simply say: "These internal services communicate over gRPC for performance and type safety." That's enough to show you know the pattern without spending precious interview time on internal API contracts.

Interview Expectations: Junior vs. Senior

  • Junior/Mid-level: Understands that RPC allows calling remote functions as if they were local. Might think of it purely through older paradigms like SOAP or XML-RPC.
  • Senior/Staff: Recognizes that RPC tightens coupling between client and server, trading flexibility (REST) for performance and strict contracts. Understands that hiding the network behind local function calls can be dangerous if the developer forgets to handle network failures, retries, and latency.