Cloud Explainers

REST vs gRPC: Which One Should You Use?

APIs10 min videoPublished Nov 8, 2026

REST and gRPC are two ways for one program to ask another for something. We built one tea-shop stock service with both doors and measured them for real: answer size, speed over 10,000 calls, streaming, what happens when a field is renamed or renumbered, and why browsers need gRPC-Web. Then which one to pick.

REST vs gRPC: Which One Should You Use?, the full picture: diagram of Web page, Order service, REST, gRPC, Tea stock, Ana's stock service
The whole picture, from the video's recap.

Two ways to ask one question

Your app needs to ask another service a question, and someone says: use gRPC, it's faster than REST. Here's Ana's tea shop, asking its stock service how much Sencha is left. Over REST: a plain address, and an answer you can read. The same question to the gRPC port, with the same curl: an error.

laptop: Ana's order service
(.venv) ana@laptop:~/order-service$ curl -s stock:8000/teas/sencha{"name":"Sencha","stock":12,"cents":650}
laptop: Ana's order service
(.venv) ana@laptop:~/order-service$ curl -sS stock:50051/teas/senchacurl: (1) Received HTTP/0.9 when not allowed

Both are ways for one program to ask another for something. REST sends a request to an address and gets text back. gRPC calls a function on the other machine, using a contract both sides share. By the end you'll know which one to use, how much faster gRPC really was in our test, and why one tiny edit made it report zero tea in stock.

Someone asked exactly this on Stack Overflow in 2017: REST versus gRPC, when should I choose one over the other? People still ask it, so let's answer it with a real run, one question at a time.

A real question, from Stack Overflow: cards of REST vs gRPC, "When should I choose", Still asked, Our answer

How REST asks

What does REST actually do? It treats each thing as a resource with an address: this tea lives at slash teas slash sencha. You ask with an HTTP verb, GET to read, POST to create, and the answer usually comes back as JSON, plain text.

REST: a verb and an address: diagram of Orders, Stock, GET /teas/sencha, 200 OK + JSON, POST /orders, a JSON reply, Any language, any tool that speaks HTTP can ask.

In Ana's order service that's three lines of Python: get the address, read the JSON, print the stock. Twelve in stock.

laptop: Ana's order service
(.venv) ana@laptop:~/order-service$ cat check_rest.py# Ana's order service asks the stock service over REST: a plain HTTP GET that answers JSON.
import httpx

tea = httpx.get("http://stock:8000/teas/sencha").json()
print(f"{tea['name']}: {tea['stock']} in stock")
laptop: Ana's order service
(.venv) ana@laptop:~/order-service$ python3 check_rest.pySencha: 12 in stock

Look at what came back. Every answer spells out its field names: name, stock, cents, every single time. Even the twelve travels as two text characters. That's why JSON is easy to read, and it's where the bytes go.

One REST answer, as text: diagram of {"name":"Sencha","stock":12,"cents":650}, "name" = a field name, "stock" = another one, 12 = a number, as text, "cents" = and another, 40 bytes, readable by anyone

How gRPC asks

gRPC starts somewhere else: a contract. A file, stock.proto, lists the functions the stock service offers, GetTea and WatchStock, and the exact shape of every message they send.

laptop: Ana's order service
(.venv) ana@laptop:~/order-service$ cat stock.protosyntax = "proto3";

service Stock {
  rpc GetTea (TeaRequest) returns (Tea);
  rpc WatchStock (TeaRequest) returns (stream Tea);
}

message TeaRequest {
  string name = 1;
}

message Tea {
  string name = 1;
  int32 stock = 2;
  int32 cents = 3;
}

Each field in that contract has three parts. A type: here, a whole number. A name, which your code uses. And a number, two. That equals sign doesn't set the stock to two: two is the field's number. Remember it, because it's the part that actually travels between the machines.

One line of the contract: diagram of int32 stock = 2;, int32 = the type, stock = the name, 2 = the field number, The name stays in your code. The number goes on the wire.

Then a tool reads the contract and writes code for you: two new Python files. One holds the message shapes; the other holds a stub, a ready-made client. Ana's script calls GetTea on that stub like a normal function, and gets the same answer: twelve.

laptop: Ana's order service
(.venv) ana@laptop:~/order-service$ python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. stock.proto
laptop: Ana's order service
(.venv) ana@laptop:~/order-service$ lsbench.py  check_grpc.py  check_rest.py	sizes.py  stock.proto  stock_pb2.py  stock_pb2_grpc.py
laptop: Ana's order service
(.venv) ana@laptop:~/order-service$ cat check_grpc.py# The same question over gRPC: call GetTea like a function, with code generated from stock.proto.
import grpc
import stock_pb2
import stock_pb2_grpc

stub = stock_pb2_grpc.StockStub(grpc.insecure_channel("stock:50051"))
tea = stub.GetTea(stock_pb2.TeaRequest(name="sencha"))
print(f"{tea.name}: {tea.stock} in stock")
laptop: Ana's order service
(.venv) ana@laptop:~/order-service$ python3 check_grpc.pySencha: 12 in stock

Underneath, gRPC sends that call as an HTTP/2 request, the newer version of the web's protocol, and the message as protobuf: compact binary, not text. That's why plain curl failed at the start. It spoke the older HTTP/1.1, and got back bytes it couldn't read as an HTTP reply.

gRPC: a function call, in binary: diagram of Orders, Stock, GetTea("sencha"), Tea, as protobuf, curl, HTTP/1.1, curl: Received HTTP/0.9 when not allowed

To try a gRPC service by hand, you need a tool that knows the contract, like grpcurl. Give it the proto file, and it shows the answer as JSON, for humans. And the reply's content type? Application slash g r p c.

laptop: Ana's order service
(.venv) ana@laptop:~/order-service$ grpcurl -plaintext -proto stock.proto -d '{"name": "sencha"}' stock:50051 Stock/GetTea{
  "name": "Sencha",
  "stock": 12,
  "cents": 650
}
laptop: Ana's order service
(.venv) ana@laptop:~/order-service$ grpcurl -v -plaintext -proto stock.proto -d '{"name": "sencha"}' stock:50051 Stock/GetTea | grep content-typecontent-type: application/grpc

This isn't only for small shops. The Cloud Native Computing Foundation's Netflix case study says a huge part of Netflix's internal service-to-service traffic runs on gRPC. Before that, Netflix ran its own stack on HTTP/1.1, and today all new Java development there starts with gRPC.

gRPC in production: Netflix: cards of CNCF case study, Inside Netflix, Before, Today

Smaller and faster, by how much?

So is it really smaller? Ana's script asks both doors for the same Sencha and counts the bytes. JSON: 40 bytes. Protobuf: 13, about a third.

laptop: Ana's order service
(.venv) ana@laptop:~/order-service$ python3 sizes.pyJSON:     40 bytes  {"name":"Sencha","stock":12,"cents":650}
protobuf: 13 bytes  0a 06 53 65 6e 63 68 61 10 0c 18 8a 05

Here are those 13 bytes. The first says field one, text, and the next says six letters follow: Sencha. Then field two: twelve. Then field three: six hundred and fifty. No field names at all, just the numbers from the contract.

One gRPC answer, as protobuf bytes: diagram of 0a 06 53 65 6e 63 68 61 10 0c 18 8a 05, 0a 06 = field 1, 6 letters, 53 65 6e 63 68 61 = Sencha, 10 0c = field 2: 12, 18 8a 05 = field 3: 650

Smaller, but is it faster? Ana's script asks each door ten thousand times in a row. Guess first: how much faster is gRPC? About one and a half times. REST took 2.58 seconds, gRPC 1.75.

laptop: Ana's order service
(.venv) ana@laptop:~/order-service$ python3 bench.py 10000REST: 10,000 calls in 2.58 s  (0.26 ms each)
gRPC: 10,000 calls in 1.75 s  (0.18 ms each)

Per call, that's 0.26 milliseconds against 0.18. Real, but small: across a real network, the trip itself often takes longer than either.

Milliseconds per call (lower is faster): chart of REST + JSON: 0.26 ms, gRPC + protobuf: 0.18 ms, 1.5x faster, both under a millisecond

So where does the saving come from? Fewer bytes to send. No text to turn back into numbers: the generated code reads the binary directly. It adds up when services call each other thousands of times a second, and hardly matters for a page a person clicks once.

Where the saving comes from: cards of Fewer bytes, No text to parse, Matters at scale, Not for one click

When the answer keeps changing

Now a harder question: Ana wants to know the moment Sencha runs low. With plain REST you ask again and again, and mostly hear the same answer. gRPC can stream: one call, and the server keeps sending each change.

Asking again, or one call that stays open: diagram of Orders, Stock, GET, every few seconds, 12 (no change), WatchStock("sencha"), 12, 10, then 8

In a second window, Ana starts WatchStock. The first message arrives at once: twelve. And the call stays open.

laptop: a second window
(.venv) ana@laptop:~/order-service$ grpcurl -plaintext -proto stock.proto -d '{"name": "sencha"}' stock:50051 Stock/WatchStock{
  "name": "Sencha",
  "stock": 12,
  "cents": 650
}

Back in her first window, two customers each buy two packs of Sencha, through the REST door. Ten left, then eight.

laptop: Ana's order service
(.venv) ana@laptop:~/order-service$ curl -s stock:8000/orders -H "Content-Type: application/json" -d '{"tea": "sencha", "qty": 2}'{"ok":true,"tea":"Sencha","left":10}
laptop: Ana's order service
(.venv) ana@laptop:~/order-service$ curl -s stock:8000/orders -H "Content-Type: application/json" -d '{"tea": "sencha", "qty": 2}'{"ok":true,"tea":"Sencha","left":8}

And the watching window? It got both changes, ten and eight, the moment they happened, without asking again.

laptop: a second window
(.venv) ana@laptop:~/order-service$ grpcurl -plaintext -proto stock.proto -d '{"name": "sencha"}' stock:50051 Stock/WatchStock{
  "name": "Sencha",
  "stock": 12,
  "cents": 650
}
{
  "name": "Sencha",
  "stock": 10,
  "cents": 650
}
{
  "name": "Sencha",
  "stock": 8,
  "cents": 650
}

That stream is one of four kinds of call a gRPC contract can have. One question, one answer, like GetTea. One question, and a stream of answers, like WatchStock. A stream of questions in, say a phone uploading readings. Or streams both ways at once, like a chat.

The four kinds of gRPC call: cards of Unary, Server streaming, Client streaming, Bidirectional

When the API changes

Every API changes. Ana's team renames the stock field to in stock, on both doors. The field number stays two. But the order service still runs its old code.

stock: Ana's stock service
(.venv) ana@stock:~/stock-service$ diff stock.proto v2/stock.proto14c14
<   int32 stock = 2;
---
>   int32 in_stock = 2;
stock: Ana's stock service
(.venv) ana@stock:~/stock-service$ python3 shop_server.py --v2stock service v2 ready: REST on port 8000, gRPC on port 50051

The REST answer now says in stock. Guess first: old clients, new server. Which one still works? Not REST: the JSON says in stock, and Ana's old script asks for stock: a KeyError, and the order service stops.

laptop: Ana's order service
(.venv) ana@laptop:~/order-service$ curl -s stock:8000/teas/sencha{"name":"Sencha","in_stock":12,"cents":650}
laptop: Ana's order service
(.venv) ana@laptop:~/order-service$ python3 check_rest.pyTraceback (most recent call last):
  File "/home/ana/order-service/check_rest.py", line 5, in <module>
    print(f"{tea['name']}: {tea['stock']} in stock")
                            ~~~^^^^^^^^^
KeyError: 'stock'

The gRPC client doesn't notice. Twelve in stock, with its old code.

laptop: Ana's order service
(.venv) ana@laptop:~/order-service$ python3 check_grpc.pySencha: 12 in stock

Why? The old client's contract says stock is field two. The new server's says in stock is field two. The bytes on the wire only say: field two, twelve. The name never travels, so both sides still agree.

Only the number travels: diagram of Old client, On the wire, New server

So what made gRPC say zero? Change the number instead, from two to four, and run the same old client.

stock: Ana's stock service
(.venv) ana@stock:~/stock-service$ diff stock.proto v3/stock.proto14c14
<   int32 stock = 2;
---
>   int32 in_stock = 4;

Sencha: zero in stock. No error, no warning, just a wrong number that looks right.

laptop: Ana's order service
(.venv) ana@laptop:~/order-service$ python3 check_grpc.pySencha: 0 in stock

The server now sends field four. The old client looks for field two, finds nothing, and proto3 fills in the default for a missing number: zero. That's why the protobuf guide says a field number can't change once it's in use.

A changed number: a quiet zero: diagram of Old client, On the wire, New server

So the two fail differently. REST breaks loudly when a name changes. gRPC survives a rename, but a changed number gives quiet, wrong answers. With either one, add new fields and keep the old ones.

When the API changes: comparison table of REST + JSON vs gRPC + protobuf, Rename a field, Renumber it, Safe change

Where gRPC is harder

So why not use gRPC everywhere? The browser. Page code can't reach the raw HTTP/2 frames gRPC needs, so a web page uses gRPC-Web, and a proxy such as Envoy translates in between. REST needs none of that: the browser already speaks it.

A web page calling gRPC: diagram of Web page, Proxy, gRPC service

It's also harder to look at. A REST answer opens in curl, a browser tab, or any language. A gRPC answer needs the proto file, generated code, or a tool like grpcurl. That's the price of the contract.

Looking inside an answer: comparison table of REST vs gRPC, Open it with, Call it from, You get

Which one to pick

So which one should you use? REST talks in readable JSON, works in every browser, and needs no contract. gRPC talks in compact binary, needs a proto file, and streams out of the box. In one line: REST is addresses and readable JSON; gRPC is typed function calls in binary.

REST or gRPC?: comparison table of REST vs gRPC, Sends, Browsers, Contract, Streaming, In one line

Use REST for anything public: web pages, partners, an API people try with curl. Use gRPC between your own services, when they talk a lot, need streaming, or are written in different languages that share one contract. Many teams run both, like Ana's stock service: REST at the edge, gRPC inside.

Every command and the lab files are linked in the description, so you can run both doors yourself.

Run it yourself: cards of shop_server.py, stock.proto, sizes.py, bench.py, grpcurl

Recap

So: JSON was 40 bytes, protobuf 13. gRPC was about 1.5 times faster, it streams, and it needs a contract you never renumber. REST stays the door for browsers.

If this settled the argument for your team, a like helps YouTube show it to more beginners, and subscribing brings the next one to you.

Every request here ran for real between two small Linux machines. Thanks for watching Cloud Explainers.

Sources

Demos: two containers on our own Mac standing in for Ana's stock service and order service; the tea shop is made up. Watch the full video on YouTube.

More videos

Linux9 min

Linux Makefiles Will Finally Make Sense After This

Auth (OAuth, JWT)9 min

SAML vs OAuth vs OpenID Connect: What's the Difference?

Python7 min

How to Fix Python cron Jobs That Never Run

Linux22 min

Linux Bash Scripting: Full Tutorial for Beginners

Networking10 min

How PXE Network Boot Works

Active Directory9 min

How Active Directory Works

Linux10 min

How to Fix a Linux systemd Service That Won't Start

Linux9 min

Linux Logs: syslog vs journald

Auth (OAuth, JWT)9 min

JWT vs Session Cookies: Which One Should You Use?

Docker10 min

Docker vs Docker Compose: Which One Should You Use?

Docker9 min

How to Fix Docker Compose Containers That Can't Talk to Each Other

Django (Python web framework)22 min

Django Full Tutorial for Beginners

Linux9 min

How Linux systemd Works

Networking8 min

How to Fix 'Windows Cannot Access' Network Share Errors (SMB)

Certificates & PKI (ADCS)9 min

How to Fix SSL Certificate Chain Errors (Missing Intermediate)

Python9 min

Every Python Concept for Beginners in 10 Minutes

Databases & SQL9 min

Every SQL Join Type in 10 Minutes

OpenRouter (one API for many AI models)9 min

Why OpenRouter Free Models Hit Their Limit So Fast

Gemini API & Google AI Studio9 min

Why Your Gemini API Free Tier Runs Out So Fast

OpenRouter (one API for many AI models)10 min

OpenRouter Will Finally Make Sense After This

Caching & Redis10 min

How Redis Caching Works

Gemini API & Google AI Studio10 min

Gemini API vs Vertex AI: Which One Should You Use?

Gemini API & Google AI Studio8 min

How to Fix a Leaked Gemini API Key

Go (Golang)9 min

Why Golang Uses Goroutines Instead of Threads

Gemini API & Google AI Studio9 min

How to Fix Gemini API Error 429: Resource Exhausted

Claude & the Claude Developer Platform10 min

Claude Token Pricing Will Finally Make Sense After This

Python11 min

Why Python Web Scraping Returns Nothing (and the Fix)

Python10 min

Python uv vs pip: Which One Should You Use?

Python22 min

Python Automation: Full Tutorial for Beginners

Auth (OAuth, JWT)9 min

How OAuth Login Works

Gemini API & Google AI Studio10 min

Google AI Studio Will Finally Make Sense After This

Docker10 min

What Happens When You Run a Docker Container

Claude & the Claude Developer Platform8 min

How to Fix Claude Code Billing Your API Key Instead of Your Plan

Auth (OAuth, JWT)9 min

Why Passkeys Can't Be Phished Like Passwords

PowerShell10 min

PowerShell vs Bash: Which One Should You Learn?

Linux10 min

The Linux Roadmap for Beginners

Django (Python web framework)9 min

Django vs FastAPI: Which One Should You Use?

Databases & SQL10 min

Why SQLite Is the Most Used Database in the World

Databases & SQL11 min

How a NoSQL Database Like MongoDB Works

Python11 min

How to Fix Python pip error: externally-managed-environment

Databases & SQL9 min

How to Fix MySQL Too Many Connections Database Errors

Go (Golang)9 min

Golang Concurrency Will Finally Make Sense After This

AWS10 min

How Amazon S3 Works

Kubernetes10 min

How Kubernetes Works

Git10 min

How Git Works

Docker10 min

How to Fix Docker Port Already Allocated Errors

PostgreSQL10 min

How PostgreSQL Works

Git8 min

How to Fix Git Merge Conflicts

Linux9 min

How to Fix No Space Left on Device in Linux

Networking10 min

How the Internet Works

Databases & SQL9 min

SQL vs NoSQL: When Each One Wins

Auth (OAuth, JWT)8 min

How to Fix JWT Token Expired Errors (401 Unauthorized)

Claude & the Claude Developer Platform9 min

Claude API vs Claude Pro: Why Your Subscription Doesn't Include It

AI agent skills9 min

Why AI Agent Skills Beat Giant Prompts

Linux10 min

Linux Cron Jobs Will Finally Make Sense After This

Cybersecurity10 min

Security Logs Will Finally Make Sense After This

Cybersecurity8 min

How to Fix a Security Leak: An API Key Pushed to GitHub

DNS9 min

DNS Will Finally Make Sense After This

Cybersecurity9 min

Never Ignore a Security Update

Auth (OAuth, JWT)9 min

Two-Factor Authentication Will Finally Make Sense

Networking8 min

Never Expose a Database to the Internet

Cybersecurity10 min

SSH Keys vs Passwords: Which Is Safer for Security?

Linux10 min

The 5 Levels of Linux Boot: From Power On to Login

Networking12 min

The 5 Levels of Network Firewalls

Cybersecurity10 min

Password Security: Why Hackers Crack Weak Passwords

Linux9 min

Why Your Linux Server Runs Out of Memory

Networking10 min

HTTP vs HTTPS: What Actually Changes on Your Network

Linux12 min

Every Linux File Permission You Need to Know

Linux10 min

Linux Processes vs Threads: When Each One Wins

Networking11 min

The OSI Model Will Finally Make Sense After This

Git11 min

The 7 Levels of Git

Networking10 min

Every Network Troubleshooting Command You Need

Cybersecurity10 min

Hashing vs Encryption in Cybersecurity: When Each One Wins

Networking11 min

Why the Internet Ran Out of IP Addresses

Linux11 min

Linux Package Managers: apt vs Snap vs Flatpak

Python9 min

Why Python Is Slow (and When It Doesn't Matter)

Networking11 min

TCP vs UDP: When Each One Wins

Linux8 min

Stop Running Linux as Root (Do This Instead)

Linux11 min

Linux Users and Groups Will Finally Make Sense

Cybersecurity9 min

Why Phishing Beats Good Security (and How to Spot It)

Docker12 min

The 7 Levels of Docker

AI agents & MCP12 min

AI Agents Will Finally Make Sense After This

Git8 min

Stop Using git push --force (Use This Instead)

AI agent skills10 min

Agent Skills vs MCP: When Each One Wins

Networking12 min

Networking Will Finally Make Sense After This

Linux12 min

Every Linux Command You Actually Need

Cybersecurity12 min

The 7 Levels of Cybersecurity

System design12 min

Every System Design Concept You Need, in One Diagram

PostgreSQL10 min

Why PostgreSQL Ignores Your Index