AlgoInsight

Free lesson · Mobile System Design Interviews

Networking Fundamentals - How Your App Talks to the World

Easy17 min readmobilemobile-system-designinterview-prep

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:

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:

Status Codes tell you if it worked:

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

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

  1. Initial handshake with HTTP upgrade (like "let's switch to a phone call")
  2. Persistent connection stays open
  3. Both directions send data freely at any time
  4. 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

The Downside

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

The Downside

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

When to Use gRPC

The Downside

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:

  1. Initial load via REST (GET /api/feed)
  2. New posts via WebSocket (real-time streaming)
  3. Like/comment via REST (POST /api/posts/123/like)
  4. 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:

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

  1. REST is the default—use it for standard APIs
  2. WebSocket for real-time—chat, games, live updates
  3. Always use HTTPS—security isn't optional
  4. Cache wisely—reduce network requests
  5. Handle failures gracefully—networks are unreliable
  6. Monitor connectivity—don't make requests when offline
  7. 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:

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:

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:

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:

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:

Disadvantage:

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:

Disadvantage:

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:

Long Polling:

WebSocket:

Server-Sent Events:

💡 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:

When to Use gRPC on Mobile

Do use if:

Don't use if:

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.

Back to Mobile System Design Interviews