In 2012, Facebook was rebuilding its mobile app. The REST API returned massive JSON payloads - a single news feed call fetched 30+ fields per post when the mobile app only needed 5. On 2G networks in emerging markets, this killed load times. Meanwhile, the feed screen required 6 separate REST calls (posts, likes, comments, user profiles, photos, ads) creating a waterfall of round trips. Facebook built GraphQL to solve both: one request, exactly the data needed. Two years later, Google faced the opposite problem - internal microservices were drowning in JSON parsing overhead. They built gRPC with binary Protocol Buffers, achieving 10x serialization speedups. Today, most large-scale systems use all three: REST for public APIs, GraphQL for client-facing data, gRPC for internal service-to-service calls.
REST's Pain Points at Scale
The Over-Fetching Problem:
Mobile app needs: { name, avatar }
GET /api/users/42
Response (2.4 KB):
{
"id": 42,
"name": "Alice",
"avatar": "...",
"email": "...", // don't need
"phone": "...", // don't need
"address": {...}, // don't need (500 bytes)
"preferences": {...}, // don't need (800 bytes)
"created_at": "...", // don't need
... 25 more fields // don't need
}
Wasted: ~2 KB per request x 1M users = 2 GB/day wasted bandwidth
The Under-Fetching Problem:
Profile page needs: user + posts + followers + photos
Request 1: GET /users/42 (120ms)
Request 2: GET /users/42/posts (200ms) // waits for #1
Request 3: GET /users/42/followers (150ms) // waits for #1
Request 4: GET /users/42/photos (180ms) // waits for #1
Request 5: GET /posts/99/comments (100ms) // waits for #2
Total: 5 round trips, ~750ms on mobile
GraphQL equivalent: 1 round trip, ~200ms
How GraphQL Works on the Server:
Client --query--> [GraphQL Server] --resolvers--> Data Sources
|
Schema Definition
(type system)
Schema (defines what is queryable):
type User {
id: ID!
name: String!
avatar: String
posts(last: Int): [Post!]!
followers: FollowerConnection!
}
type Post {
id: ID!
title: String!
likeCount: Int!
comments(first: Int): [Comment!]!
}
type Query {
user(id: ID!): User
posts(limit: Int, offset: Int): [Post!]!
}
Resolvers (fetch data per field):
Query.user(id) -> SELECT * FROM users WHERE id = ?
User.posts(user) -> SELECT * FROM posts WHERE author_id = ?
Post.comments(post) -> SELECT * FROM comments WHERE post_id = ?
N+1 Problem: 10 posts x 1 comments query each = 11 DB queries
Solution: DataLoader batches them into 1 query:
SELECT * FROM comments WHERE post_id IN (1,2,3,4,5,6,7,8,9,10)
gRPC: Binary Speed for Microservices
Protocol Buffer Definition (.proto file):
syntax = "proto3";
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc ListUsers(ListUsersRequest) returns (stream User);
rpc UpdateUser(User) returns (User);
}
message User {
int64 id = 1;
string name = 2;
string email = 3;
repeated Post posts = 4;
}
Why gRPC is Fast:
JSON (REST): { "id": 42, "name": "Alice" } = 35 bytes + parsing
Protobuf (gRPC): 08 2A 12 05 41 6C 69 63 65 = 9 bytes, zero parsing
Feature REST/JSON gRPC/Protobuf
---------------- --------------- ----------------
Serialization Text (slow) Binary (3-10x faster)
Transport HTTP/1.1 HTTP/2 (multiplexed)
Contract OpenAPI (docs) .proto (code-generated)
Streaming SSE/WebSocket Native (4 modes)
Browser support Native Needs grpc-web proxy
Debugging curl friendly Needs special tools
gRPC Streaming Modes:
1. Unary: Client sends 1, Server returns 1 (like REST)
2. Server streaming: Client sends 1, Server streams many (live feed)
3. Client streaming: Client streams many, Server returns 1 (file upload)
4. Bidirectional: Both stream simultaneously (chat/gaming)
When to Use Each Protocol
Protocol Selection Guide:
Use Case Best Choice Why
---------------------------- ----------- --------------------------
Public API (external devs) REST Universal, cacheable, simple
Mobile app data fetching GraphQL Bandwidth-efficient, flexible
Microservice-to-microservice gRPC Fast, type-safe, streaming
Browser real-time updates GraphQL Sub Built-in subscription model
File uploads REST Multipart is well-supported
Internal event streaming gRPC Bidirectional streaming
Third-party integrations REST Everyone understands REST
Complex nested data queries GraphQL Single round trip
ML model serving gRPC Low latency, binary data
Real-World Combinations:
Netflix: REST (public) + GraphQL (BFF) + gRPC (internal)
Shopify: REST (public) + GraphQL (storefront API)
Google: REST (public) + gRPC (all internal services)
GitHub: REST v3 (legacy) + GraphQL v4 (new clients)
Uber: REST (rider/driver apps) + gRPC (internal ~4000 services)
Interview Tip
When discussing API protocols in interviews, demonstrate you understand tradeoffs, not just definitions. Say: 'REST is my default for public APIs because it is universally understood and cacheable. For mobile or complex frontend data needs, I would add a GraphQL layer (or BFF) to eliminate round trips and over-fetching. For internal service communication - especially when latency matters - gRPC with Protobuf gives 3-10x faster serialization. Many companies like Netflix use all three at different layers.' Always mention the GraphQL N+1 problem and DataLoader as the solution - it shows depth.
<Architecture overview
/blockquote>
Key Takeaway
REST is simple and universal but suffers from over/under-fetching at scale. GraphQL lets clients request exactly what they need in one round trip - ideal for mobile and complex UIs. gRPC uses binary Protobuf and HTTP/2 for 3-10x faster service-to-service communication with native streaming. Most large systems combine all three at different layers. Choose based on the communication boundary: REST for external, GraphQL for client-facing, gRPC for internal.
No comments yet. Be the first to start the thread!
Related Articles
Enjoyed this content?
Your support keeps us creating free resources
We put a lot of hours into researching and writing these guides. If it helped you, consider buying us a coffee. Every bit goes toward keeping ScaleDojo's content free and growing.
$
One-time payment via Stripe. ScaleDojo account required.