Free lesson · Mobile System Design Interviews
Networking Fundamentals - How Your App Talks to the World
Your app doesn't live alone. It talks to servers, receives data, and sends updates. How it communicates makes a huge difference in user experience and battery life.
Let's explore the main ways apps chat with the outside world.
The Communication Methods Analogy
Imagine you need to stay in touch with a friend across the country:
- HTTP/REST = Mailing letters (you send, you wait for a reply, you send again)
- WebSockets = A phone call (connected once, both sides talk freely)
- Server-Sent Events = A radio broadcast (you listen, the station broadcasts to everyone)
- gRPC = A courier service (very fast, very structured, common for high-speed business)
Each has trade-offs. Let's explore them all.
HTTP/REST: The Postal Service
You send a request, the server processes it, and sends back a response. Then the connection closes. Next request = new connection.
Basic Concepts
HTTP Methods tell the server what you want to do:
- GET = Retrieve data ("Give me the user list")
- POST = Create new data ("Add this new post")
- PUT = Replace existing data ("Update this user completely")
- DELETE = Remove data ("Delete this post")
- PATCH = Partially update data ("Just change the user's name")
Status Codes tell you if it worked:
- 200-299 = Success ("Here's your data")
- 400-499 = Your fault ("Bad request", "Unauthorized", "Not found")
- 500-599 = Server's fault ("Internal error", "Service unavailable")
Common REST Status Codes at a Glance
| Code | Meaning | Example |
|---|---|---|
| 200 | OK | Request succeeded, here's the data |
| 201 | Created | New resource successfully created |
| 204 | No Content | Success, but no data to return |
| 400 | Bad Request | Invalid request format |
| 401 | Unauthorized | Not logged in |
| 403 | Forbidden | Logged in, but no permission |
| 404 | Not Found | Resource doesn't exist |
| 500 | Internal Server Error | Server messed up |
| 503 | Service Unavailable | Server overloaded or down |
Example: Fetching a User
REQUEST:
GET /api/users/42 HTTP/1.1
Host: api.example.com
Authorization: Bearer token123
RESPONSE:
200 OK
{
"id": 42,
"name": "Alice",
"email": "alice@example.com"
}
Why REST is Popular
- Simple - Straightforward request/response model
- Stateless - Each request stands alone (no connection state to manage)
- Cacheable - Responses can be cached by browsers and proxies
- Standard - Every developer knows how it works
The Downside: Overhead
Every request opens a connection, sends headers, waits for headers back. It's reliable but not super-fast for real-time communication.
Kotlin (Retrofit) & Swift (URLSession) Example
| Kotlin (Retrofit) | Swift (URLSession) |
|---|---|
interface UserApi {<br/> @GET("users/{id}")<br/> suspend fun getUser(@Path("id") id: Int): User<br/>}<br/><br/>// Usage<br/>val userApi = Retrofit.Builder()<br/> .baseUrl("https://api.example.com/")<br/> .addConverterFactory(GsonConverterFactory.create())<br/> .build()<br/> .create(UserApi::class.java)<br/><br/>val user = userApi.getUser(42) |
var request = URLRequest(url: URL(string: "https://api.example.com/users/42")!)<br/>request.httpMethod = "GET"<br/>request.setValue("Bearer token123", forHTTPHeaderField: "Authorization")<br/><br/>let session = URLSession.shared<br/>let task = session.dataTask(with: request) { data, response, error in<br/> if let data = data {<br/> let decoder = JSONDecoder()<br/> let user = try decoder.decode(User.self, from: data)<br/> }<br/>}<br/>task.resume() |
💡 Tip: Retrofit (Android) handles JSON parsing automatically. For Swift, use a library like Alamofire or combine URLSession with Codable.
WebSockets: The Phone Call
Imagine a phone call instead of mailing letters. You dial once, the connection stays open, and both sides can talk anytime.
How It Works
- Initial handshake with HTTP upgrade (like "let's switch to a phone call")
- Persistent connection stays open
- Both directions send data freely at any time
- Low latency because no reconnection overhead
Client Server
| |
|------- HTTP Upgrade -------->|
|<------ Connection OK --------|
| |
|------- Hello, is anyone there?
|<------ Yes, I'm listening ----|
| |
|------- Send me updates ----->|
|<------ Update #1 ------------|
|<------ Update #2 ------------|
Perfect For
- Real-time chat - Messages appear instantly
- Live notifications - Stock prices, game events, sports scores
- Multiplayer games - Player movements, collisions
- Collaborative editing - Multiple users editing a document simultaneously
- Live dashboards - Real-time data streams
The Downside
- More server resources - Each connection ties up server memory
- Trickier to scale - Can't easily load-balance (server must remember each client)
- Firewall complexity - Some proxies and firewalls block WebSocket
Server-Sent Events (SSE): The Radio Broadcast
The server opens a connection and broadcasts updates to all listening clients. You receive, but can't talk back through the same channel.
How It Works
Client 1 Server Client 2
| | |
|--------- Listen --------->|
| |--------- Listen ------>|
| |
| [User posts]
| |
|<----- Event: New post ----|
| |<--- Event: New post ----|
Perfect For
- Broadcasting updates to many clients (news feed, stock ticker)
- Server → Client only (notifications, progress bars, alerts)
- Simpler than WebSocket for one-way streaming
The Downside
- One-directional - Can't send commands back through SSE
- Less standard - Not every server supports it
- HTTP underneath - Still has some HTTP overhead
gRPC: The Courier Service
gRPC is a modern, high-performance protocol. It's not as universally supported as REST, but it's incredibly fast.
Why gRPC is Different
- Binary protocol (protobuf) instead of text JSON - smaller, faster
- HTTP/2 underneath - multiplexing, server push
- Typed contracts - Client and server agree on exact message format
- Built for microservices - Common in backend-heavy systems
When to Use gRPC
- Internal APIs between your frontend and microservices
- High-performance needs (real-time data streams)
- Bandwidth-constrained environments (binary is smaller than JSON)
- Strong typing matters (you want contract enforcement)
The Downside
- More complex - Need protocol buffer definitions
- Less web-friendly - Not as easy to use from browsers (requires special setup)
- Smaller ecosystem - Fewer libraries than REST
Example (Simple Concept)
// Protocol buffer definition (defines contract)
service UserService {
rpc GetUser(UserId) returns (User) {}
}
message UserId {
int32 id = 1;
}
message User {
int32 id = 1;
string name = 2;
string email = 3;
}
Polling vs Long Polling vs Streaming: The Mailbox Analogy
How often should you check for new data?
Polling: Check Every 5 Minutes
Time 0:00 → Client: "Any new messages?"
Server: "Nope"
Time 0:05 → Client: "Any new messages?"
Server: "Nope"
Time 0:10 → Client: "Any new messages?"
Server: "Yes, 3 new ones!"
Pros: Simple, doesn't strain the server Cons: Lag (up to 5 minutes), wasted requests when there's no data
Long Polling: Stand by the Mailbox
Time 0:00 → Client: "Tell me when there's something new" (request waits open)
(client just waits...)
Time 0:47 → Server: "New message arrived!" (response finally comes)
Time 0:47 → Client: "Any more?" (opens new request, waits again)
Pros: Lower latency than polling, simpler than WebSocket Cons: Still lots of request overhead, still mostly one-way
Streaming (WebSocket or Server-Sent Events): Courier at Your Door
Time 0:00 → Connection established
Time 0:05 → Server: "New message"
Time 0:10 → Server: "Another update"
Time 0:15 → Server: "New notification"
Pros: True real-time, minimal latency, efficient Cons: Trickier to implement, more server resources
Protocol Comparison: Quick Reference
| Protocol | Direction | Latency | Best For | Complexity | Overhead |
|---|---|---|---|---|---|
| REST/HTTP | One way (request/response) | Moderate (100-500ms) | CRUD operations, standard APIs | Low | Moderate |
| Long Polling | Mostly one way | Low-Moderate (30-100ms) | Near real-time without WebSocket | Low | Moderate-High |
| WebSocket | Both ways | Very Low (<50ms) | Chat, live games, real-time data | Moderate | Low |
| Server-Sent Events | Server → Client | Very Low (<50ms) | Broadcasting, live feeds | Low | Low |
| gRPC | Both ways | Very Low (<50ms) | Microservices, high-performance | High | Very Low |
Practical Decision: What Should I Use?
For a News App
Use REST/HTTP. Articles don't need to update in real-time. Simple, standard, works everywhere.
For a Chat App
Use WebSocket. Messages need to appear instantly. Bidirectional communication essential.
For a Stock Ticker App
Use Server-Sent Events or WebSocket. Server broadcasts prices constantly. One-way (SSE) might be enough, but WebSocket gives you flexibility.
For a Multiplayer Game
Use WebSocket or gRPC. Extremely low latency needed. Both players moving simultaneously.
For a Social Media Feed with Live Notifications
Use REST for initial load + WebSocket for live updates. Load the feed via REST, then use WebSocket to stream new posts.
Real-World Mobile Example: Hybrid Approach
Most production apps use a combo:
- Initial load via REST (GET /api/feed)
- New posts via WebSocket (real-time streaming)
- Like/comment via REST (POST /api/posts/123/like)
- Offline resilience with local cache (Room/Core Data)
This balances simplicity, performance, and reliability.
Headers: The Envelope Around Your Data
Every HTTP request and response includes headers—metadata about the message.
Common Request Headers
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
Accept: application/json
User-Agent: MyApp/1.0 (Android)
Authorization - Proves who you are Content-Type - What format your body is in Accept - What format you want back User-Agent - Identifies your app/client
Common Response Headers
Content-Type: application/json
Cache-Control: max-age=3600
Set-Cookie: sessionId=abc123; Path=/
X-RateLimit-Remaining: 4999
Cache-Control - How long can this be cached? Set-Cookie - Server setting a cookie X-RateLimit-Remaining - How many requests left before rate limit?
HTTP/HTTPS
HTTP = Unencrypted, anyone can read the data HTTPS = Encrypted, secure
Always use HTTPS for your APIs. Never transmit sensitive data (passwords, tokens) over HTTP.
❌ http://api.example.com/login?password=secret123
✅ https://api.example.com/login (password in encrypted body)
⚠️ Watch out: Some networks (public WiFi) can intercept HTTP traffic. Always use HTTPS.
Mobile Networking Best Practices
Minimize Requests
Each request costs battery and data. Combine multiple pieces of data into one call when possible.
// Bad: Three separate requests
api.getUser(42)
api.getUserPosts(42)
api.getUserFriends(42)
// Better: One request that returns everything
api.getUserFull(42) // Returns user + posts + friends
Implement Retry Logic
Networks fail. Retry failed requests a few times before giving up.
var retries = 0
while (retries < 3) {
try {
return api.getUser(42)
} catch (e: IOException) {
retries++
if (retries >= 3) throw e
Thread.sleep(1000) // Wait before retry
}
}
Cache Responses Locally
Don't fetch the same data repeatedly. Store it locally and check freshness.
fun getUser(id: Int): User {
// Check local database first
val cached = database.getUser(id)
if (cached != null && cached.isStillFresh()) {
return cached
}
// If stale or missing, fetch from network
val fresh = api.getUser(id)
database.saveUser(fresh)
return fresh
}
Monitor Connectivity
Don't try to make requests when the network is down.
// Android
val isConnected = ConnectivityManager.activeNetworkInfo?.isConnected ?: false
if (isConnected) {
fetchData()
} else {
showOfflineMessage()
}
Implement Timeouts
Don't wait forever for a response that never comes.
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.build()
)
.build()
Interview Tips: Networking Questions
They Might Ask:
-
"How would you implement a chat feature?"
- Answer: "I'd use WebSocket for real-time bidirectional communication. It keeps a persistent connection open, allowing messages to be sent and received instantly. For resilience, I'd queue failed messages locally and retry when the connection is restored."
-
"How would you handle a slow network?"
- Answer: "I'd implement retry logic with exponential backoff, cache successful responses locally, and show clear loading states to the user. I'd also minimize the amount of data per request and consider pagination for large datasets."
-
"When would you use REST vs WebSocket?"
- Answer: "REST for standard CRUD operations and initial data loading—it's simple and widely supported. WebSocket for real-time features like chat or live updates where latency and bidirectional communication matter. Often I'd use both together."
-
"How do you handle authentication in network requests?"
- Answer: "I store the auth token securely (Keychain/Secure Storage), then attach it to every request via the Authorization header. I implement refresh token logic so when the short-lived access token expires, I automatically request a new one without the user noticing."
What NOT to Say
❌ "I don't think about network performance" ❌ "I make a new request every time without caching" ❌ "I send passwords over HTTP"
What TO Say
✅ "I balance simplicity with performance—REST for standard APIs, WebSocket for real-time. I cache responses, handle retries, and always use HTTPS. I also think about the user on a slow 3G connection."
Key Takeaways
- REST is the default—use it for standard APIs
- WebSocket for real-time—chat, games, live updates
- Always use HTTPS—security isn't optional
- Cache wisely—reduce network requests
- Handle failures gracefully—networks are unreliable
- Monitor connectivity—don't make requests when offline
- Think about latency—what protocol fits your use case?
Your networking choices directly impact user experience and battery life. Choose wisely!
How HTTP Actually Works: The Technical Journey
When you make an HTTP request, a lot happens under the hood. Understanding this helps you debug, optimize, and choose the right tool.
TCP Handshake: The Three-Way Handshake
Before any data is sent, TCP must establish a connection:
Client Server
| |
|------- SYN (seq=100) ------------->|
| "I want to talk" |
| |
|<------ SYN+ACK (seq=200, ack=101) -|
| "OK, I'm listening" |
| |
|------- ACK (seq=101, ack=201) ---->|
| "Got it, let's talk" |
| |
| Connection established! |
Time cost: ~50-100ms for local networks, higher for distant servers.
This is why connection reuse (HTTP/1.1 keep-alive, HTTP/2) matters. You don't want to do this handshake for every single request.
TLS Negotiation: Encryption Setup
If using HTTPS (which you always should!), TLS must be negotiated:
Client Server
|-- ClientHello (ciphers offered) -->|
| |
|<-- ServerHello (pick cipher) ------|
|<-- ServerCertificate --------------|
|<-- ServerKeyExchange -------------|
|<-- ServerHelloDone -------------|
| |
|-- ClientKeyExchange ------------->|
|-- ChangeCipherSpec ------------->|
|-- Finished (encrypted) ---------->|
| |
|<-- ChangeCipherSpec --------------|
|<-- Finished (encrypted) <---------|
| |
| Secure connection ready! |
Time cost: ~100-200ms (including TCP handshake before it).
Why HTTPS takes longer than HTTP:
- TCP handshake
- TLS negotiation
- Certificate validation
- Encryption overhead
But security is worth it!
Full Request/Response Lifecycle with Timing
T=0ms
Client starts DNS lookup (What's the IP for api.example.com?)
T=50ms
DNS resolved: api.example.com = 1.2.3.4
Client initiates TCP connection to 1.2.3.4:443
T=100ms
TCP connection established
Client starts TLS negotiation
T=200ms
TLS handshake complete, encrypted connection ready
Client sends HTTP request:
POST /api/users HTTP/1.1 Host: api.example.com Content-Type: application/json [empty line] {"name":"Alice"}
T=250ms
Server receives request
Server processes (queries database, etc.)
T=300ms
Server sends response:
HTTP/1.1 201 Created Content-Type: application/json [empty line] {"id":42,"name":"Alice"}
T=350ms
Client receives response
Client parses JSON
App updates UI
Total time: 350ms
Breakdown:
- DNS: 50ms
- TCP: 50ms
- TLS: 100ms
- Actual request: 50ms
- Server processing: 50ms
- Response transmission: 50ms
On a slow network, any of these could double or triple. On a fast local network, all are sub-50ms.
💡 Tip: Connection pooling and reuse in HTTP/1.1 skip the TCP and TLS setup on subsequent requests. This is why HTTP/2 multiplexing is so important—you don't waste time on new connections.
WebSocket vs SSE vs Polling: Deep Comparison
Let me compare these with real-world scenarios and latency characteristics.
Scenario: Monitoring a Real-Time Leaderboard
Polling every 5 seconds:
T=0s: Client: "What's the leaderboard?" → Server responds
(Network: 200ms round trip)
T=5s: Client: "What's the leaderboard?" → Server responds
T=10s: Client: "What's the leaderboard?" → Server responds
Problems:
- If player 1 takes gold at T=3.5s, player 2 doesn't see it until T=5s
- Maximum latency: 5 seconds
- 5 network requests per 25 seconds of watching
- Battery drain from constant requests
Long Polling:
T=0s: Client: "Tell me when leaderboard changes" (request stays open)
(Server waits)
T=2.3s: Player scores, server pushes response
Client immediately receives update
T=2.3s: Client immediately requests again
T=5.1s: Another score, server responds
Improvements:
- Latency: ~0-100ms (very responsive)
- Fewer requests when data is stable
- Still causes churn if data changes constantly
WebSocket:
T=0s: Client: "Upgrade to WebSocket" → Handshake
(Connection stays open)
T=0.1s: Connection ready
T=2.3s: Player scores
Server instantly pushes: "New score: Player 5 = 10000"
Client receives instantly
No new connection needed
T=5.1s: Another score
Server pushes instantly
Advantages:
- True bidirectional
- Sub-50ms latency
- Efficient (one connection for many updates)
- Server can push to client anytime
Disadvantage:
- Uses more server memory (connection per client)
Server-Sent Events:
T=0s: Client: "Stream events" → HTTP connection upgrade
(Connection stays open, one-way from server)
T=0.1s: Connection ready
T=2.3s: Player scores
Server: sends "data: New score..."
Client receives
T=5.1s: Another score
Server: sends "data: Another score..."
Client receives
Advantages:
- Simple (just HTTP, no special protocol)
- One-way from server → clients
- Uses less server memory than WebSocket
- Built-in reconnection
Disadvantage:
- Client can't easily send data back (would need separate REST calls)
Decision Matrix
| Feature | Polling | Long Polling | WebSocket | SSE |
|---|---|---|---|---|
| Latency | 5 sec | 100ms | 20ms | 50ms |
| Battery Cost | High (constant requests) | Medium | Low | Low |
| Bidirectional | No | Mostly one-way | Yes | No |
| Server Load | Low (stateless) | Low (per-request state) | Medium (connection per client) | Low (per-stream) |
| Scalability | Excellent | Good | Fair (can handle thousands) | Good |
| Browser Support | Perfect | Perfect | Perfect | Good (all modern) |
| Mobile-Friendly | Poor (drains battery) | OK | Good | Good |
| Implementation | Trivial | Easy | Moderate | Easy |
Real-World Use Cases
Polling:
- Status dashboard checked every 30 seconds
- Batch sync operations where freshness doesn't matter much
- Polling is OK if you need data less frequently than every 10 seconds
Long Polling:
- Not often used anymore (WebSocket/SSE are better)
- Legacy systems that can't upgrade to WebSocket
- Good middle ground when WebSocket isn't available
WebSocket:
- Multiplayer games (need bidirectional, low latency)
- Chat apps (both sides need to send and receive)
- Real-time collaborative editing
- Stock trading apps (need instant updates + ability to execute orders)
Server-Sent Events:
- Live notifications
- Server sending progress updates (uploading file, processing video)
- Live sports scores pushed to all watching clients
- Real-time dashboards where clients just watch
💡 Tip: For new projects, choose WebSocket or SSE. Polling is only acceptable if your data changes rarely or freshness isn't critical.
Battery Impact on Mobile
Polling (every 5 seconds):
- Network radio on: 5s
- Processing: 0.5s
- Sleep: 4.5s
Result: Radio always active, constant drain
Long Polling:
- Connection open: batches updates together
- Radio can sleep between updates
- Better than polling
WebSocket:
- One persistent connection
- Radio stays on, but shared connection
- Updates pushed instantly when needed
- Efficient
SSE:
- Similar to WebSocket
- Slightly more efficient (simpler protocol)
How REST APIs Are Designed: Richardson Maturity Model
Not all APIs are equal. The Richardson Maturity Model shows levels of REST maturity.
Level 0: RPC (Remote Procedure Call)
API is just a URL that you POST to with an action:
POST /api HTTP/1.1
{
"action": "getUser",
"params": { "id": 42 }
}
POST /api HTTP/1.1
{
"action": "deleteUser",
"params": { "id": 42 }
}
Problem: Not really REST. You're calling functions, not using HTTP semantics. Looks same whether you're reading or deleting.
Level 1: Resources
Now we use different URLs for different resources:
POST /users HTTP/1.1
{ "id": 42, "name": "Alice" }
GET /users/42 HTTP/1.1
DELETE /users/42 HTTP/1.1
Better: Clearly different URLs for different things. But we're still using POST for everything.
Level 2: HTTP Verbs
Now we use the right HTTP method for the action:
GET /users/42 ← Retrieve (safe, idempotent)
POST /users ← Create (creates new resource)
PUT /users/42 ← Replace entire resource (idempotent)
PATCH /users/42 ← Partially update (idempotent)
DELETE /users/42 ← Delete (idempotent)
Much better: The method tells you what's happening. Proper use of HTTP status codes:
GET /users/42
├─ 200 OK → Resource found, here's the data
├─ 404 Not Found → No user with id=42
└─ 500 Server Error → Server broke
POST /users
├─ 201 Created → User created, here's the new data
├─ 400 Bad Request → Invalid input
└─ 409 Conflict → Email already exists
Level 3: HATEOAS (Hypermedia)
Responses include links to next actions:
GET /users/42
HTTP 200 OK
{
"id": 42,
"name": "Alice",
"_links": {
"self": { "href": "/users/42" },
"posts": { "href": "/users/42/posts" },
"friends": { "href": "/users/42/friends" },
"update": { "href": "/users/42", "method": "PATCH" }
}
}
Benefit: Client doesn't need to know URL structure. Server tells you what's possible. Helps versioning—server can change URLs without breaking clients.
Problem: Not widely adopted in mobile APIs. Most mobile APIs are Level 2.
💡 Tip: Aim for Level 2 (HTTP Verbs) in your APIs. HATEOAS is nice but adds complexity that mobile clients don't always need.
Idempotency: Why It Matters
Idempotent = calling it multiple times has the same effect as calling it once.
GET /users/42
├─ Idempotent: Call it 100 times, always get same response
├─ No side effects
POST /users
├─ NOT idempotent: Call it 3 times, creates 3 users
├─ Side effects!
PUT /users/42
├─ Idempotent: Calling it 3 times with same data = same result
├─ Third request sees no change
PATCH /users/42
├─ Usually idempotent (depends on what it does)
├─ { "age": 30 } called twice = same result
DELETE /users/42
├─ Idempotent: First delete succeeds, second returns 404 (already deleted)
├─ Same final state
Why this matters: Networks can fail mid-request. Your app might retry. If the first request actually went through but timeout before response came back:
Client: POST /api/payment {"amount": 100}
Server: Charges 00
Network: Response lost before reaching client
Client timeout: Retries POST /api/payment {"amount": 100}
Server: Charges another 00!
Problem: $200 charged, not 00
Solution: Use idempotency keys:
POST /api/payment
{
"amount": 100,
"idempotencyKey": "txn_12345"
}
Client retry:
POST /api/payment
{
"amount": 100,
"idempotencyKey": "txn_12345"
}
Server: "I've already processed txn_12345, returning cached response"
Result: 00 charged, not $200
gRPC and Protocol Buffers: When to Use Them Over REST
gRPC is designed for fast internal communication between services. Should your mobile app use it?
gRPC vs REST Comparison
REST (JSON over HTTP/1.1):
Request size: ~500 bytes
Response size: ~1 KB
Latency: 200-300ms
Step-by-step:
1. DNS lookup
2. TCP handshake
3. TLS negotiation
4. HTTP request (text parsing)
5. JSON parsing
6. JSON encoding of response
7. Send response
8. JSON parsing on client
gRPC (Protocol Buffers over HTTP/2):
Request size: ~100 bytes
Response size: ~200 bytes
Latency: 50-100ms
Step-by-step:
1. DNS lookup (reused connection)
2. TLS negotiation (reused connection)
3. gRPC request (binary)
4. Protobuf binary parsing (fast)
5. Send response (binary)
6. Protobuf parsing on client
Binary vs Text Protocols
Text (JSON):
{
"id": 42,
"name": "Alice",
"age": 30,
"active": true
}
Size: 59 bytes
Binary (Protocol Buffers):
[field_id:1, value:42]
[field_id:2, string:"Alice"]
[field_id:3, value:30]
[field_id:4, bool:true]
Size: ~15 bytes
Binary is much more compact because:
- No quotes or colons
- Numbers encoded as bytes, not text
- Field names replaced with numeric IDs
When to Use gRPC on Mobile
Do use if:
- Your mobile app is part of a larger microservices ecosystem where gRPC is standard
- You have strict latency requirements (game, trading platform)
- Bandwidth is expensive (users on metered connections)
- You're willing to add complexity (code generation, build setup)
Don't use if:
- You're building a simple CRUD app
- Your team doesn't know gRPC
- Debugging is important (binary format is hard to inspect)
- You need to integrate with web clients
- Your API is public-facing (most REST, not gRPC)
Protocol Buffers vs JSON Example
// Protocol Buffer definition
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc UpdateUser(UpdateUserRequest) returns (User);
}
message GetUserRequest {
int32 user_id = 1;
}
message User {
int32 id = 1;
string name = 2;
string email = 3;
repeated Post posts = 4;
}
message Post {
int32 id = 1;
string content = 2;
int64 timestamp = 3;
}
message UpdateUserRequest {
int32 user_id = 1;
string name = 2;
string email = 3;
}
Generated Kotlin code (from protobuf):
val user = userService.getUser(
GetUserRequest.newBuilder().setUserId(42).build()
)
println(user.name)
println(user.postsList.size)
Strongly typed, compact, efficient.
Equivalent REST API:
GET /api/users/42
→ JSON response, manually parse, pray field names don't change
💡 Tip: For most mobile apps, REST is fine. gRPC is worth learning, but don't add it just because it's trendy. Use it when it solves a real problem.