Chapter 3: Networking Deep Dive

L4 vs L7, Connection Pooling, Keep-Alive, Head-of-Line Blocking

Introduction

Network behavior is the foundation of distributed systems. Most system design failures trace back to misunderstanding network fundamentals: TCP mechanics, connection lifecycle, protocol overhead, and failure modes.

This chapter covers the critical networking concepts you need for both building production systems and acing FAANG interviews. We go deep on Layer 4 vs Layer 7 proxying, connection pooling strategies, HTTP keep-alive mechanics, head-of-line blocking across protocols, and data center networking topologies.

Why This Matters for System Design

Understanding networking depth separates senior engineers from junior ones. When you design a system handling 100K RPS, knowing that each TCP connection costs 3-way handshake (1.5 RTT) + TLS handshake (2 RTT) = 3.5 RTT means you can calculate that without connection pooling, you'd spend 35ms just on connection setup for a 10ms cross-region RTT. That's 350ms of latency before any application logic runs.

L4: Transport Layer Networking

Layer 4 operates at the TCP/UDP level. It sees ports, sequence numbers, and connection state, but not application protocols like HTTP.

TCP vs UDP Multiplexing

Both TCP and UDP multiplex multiple application connections over a single network interface using port numbers.

graph TB subgraph "Application Layer" A1[App 1
Port 8080] A2[App 2
Port 8081] A3[App 3
Port 8082] end subgraph "Transport Layer" TCP[TCP Layer
Demultiplexes by port] end subgraph "Network Layer" IP[IP Layer
Single interface: eth0] end A1 --> TCP A2 --> TCP A3 --> TCP TCP --> IP IP --> N[Network
Packets with dst port] style TCP fill:#2374ab style IP fill:#1d5a7a
Feature TCP UDP
Connection State Stateful: SYN/ACK handshake, sequence numbers Stateless: no connection setup
Reliability Guaranteed delivery, retransmission Best-effort, no retransmission
Ordering In-order delivery guaranteed No ordering guarantee
Overhead 20+ bytes header, connection state memory 8 bytes header, minimal state
Use Cases HTTP, database connections, file transfer DNS, video streaming (tolerates loss), QUIC foundation
Setup Latency 1.5 RTT (3-way handshake) 0 RTT (can send data immediately)

Socket Buffers & Backlog Queue

Each TCP connection has kernel-managed buffers for sending and receiving data. The accept backlog queue holds completed connections waiting for application to accept().

sequenceDiagram participant C as Client participant K as Kernel (Server) participant A as Application (listen fd) Note over K: SYN backlog (half-open) C->>K: SYN Note over K: Connection enters SYN backlog K->>C: SYN-ACK C->>K: ACK Note over K: Move to accept backlog (completed) Note over K: Accept backlog (ready for accept()) A->>K: accept() K->>A: Returns connection fd Note over A: Application now owns connection C->>K: Data packets Note over K: Data goes to recv buffer K->>A: Application reads from recv buffer

Real Numbers: Socket Buffer Sizing

  • Default TCP recv buffer: 128KB-256KB (Linux)
  • Max TCP recv buffer: 4MB-16MB (tunable via sysctl)
  • Accept backlog: 128-4096 connections typical
  • SYN backlog: 1024-8192 (sysctl net.ipv4.tcp_max_syn_backlog)

SYN Flood Protection: SYN Cookies

SYN flood attacks send many SYN packets without completing the handshake, exhausting the SYN backlog. SYN cookies encode connection state in the sequence number, avoiding memory allocation.

graph LR C1[Client] -->|1. SYN| S1[Server] S1 -->|"2. Allocate memoryltbr/gtfor connection state"| M1[Memory] S1 -->|3. SYN-ACK| C1 C1 -->|4. ACK| S1 C2[Attacker] -->|"SYN SYN SYNltbr/gt(never sends ACK)"| S2[Server] S2 -->|Exhausts| M2[SYN backlog FULL] L[Legitimate Client] -->|SYN| S2 S2 -.->|DROPPED| L style M2 fill:#d9534f style L fill:#d9534f
sequenceDiagram participant C as Client participant S as Server (SYN cookies enabled) Note over S: No memory allocated yet! C->>S: SYN (seq=X) Note over S: Generate cookie: HMAC(src_ip, src_port, dst_ip, dst_port, secret) S->>C: SYN-ACK (seq=cookie, ack=X+1) C->>S: ACK (ack=cookie+1) Note over S: Validate cookie, NOW allocate memory Note over S: Connection established

Failure Mode: SYN Backlog Exhaustion

Symptom: New connections timing out, but existing connections work fine.

Diagnosis: netstat -s | grep "SYNs to LISTEN" shows drops.

Fix: Increase net.ipv4.tcp_max_syn_backlog or enable net.ipv4.tcp_syncookies=1.

TIME_WAIT State and Its Implications

When a TCP connection closes, the side that initiates the close (sends FIN first) enters TIME_WAIT state for 2*MSL (Maximum Segment Lifetime, typically 60-120 seconds). This prevents delayed packets from old connections from being misinterpreted.

sequenceDiagram participant C as Client participant S as Server Note over C,S: Connection active C->>S: FIN (client closes) S->>C: ACK Note over C: FIN_WAIT_1 β†’ FIN_WAIT_2 S->>C: FIN (server closes) C->>S: ACK Note over C: TIME_WAIT (2*MSL = 60-120s) Note over S: CLOSED immediately Note over C: Remains in TIME_WAIT
to catch delayed packets Note over C: After 2*MSL, CLOSED

Real Numbers: TIME_WAIT Accumulation

  • TIME_WAIT duration: 60s (Linux default)
  • Max connections per IP:port pair: ~64K (ephemeral port range)
  • Connections per second sustainable: 64K / 60s = ~1,000 conn/sec per server IP
  • At 10K conn/sec: Need 10 server IPs or connection reuse
Problem Symptom Solution
TIME_WAIT accumulation (client) Cannot establish new connections: "Address already in use" Enable SO_REUSEADDR, use connection pooling
TIME_WAIT accumulation (server) Many connections in TIME_WAIT state Usually harmless (server has many ports), but can tune tcp_tw_reuse
Port exhaustion All ephemeral ports used Increase net.ipv4.ip_local_port_range, use multiple IPs

SO_REUSEPORT: Load Balancing Across Processes

SO_REUSEPORT (Linux 3.9+) allows multiple processes to bind to the same port. The kernel distributes incoming connections across them, enabling per-process accept queues.

graph TB subgraph "Without SO_REUSEPORT" L1[Master Process
listen on :80] L1 --> W1[Worker 1
accept from shared queue] L1 --> W2[Worker 2
accept from shared queue] L1 --> W3[Worker 3
accept from shared queue] Note1[Single accept queue
Thundering herd on new connection] end subgraph "With SO_REUSEPORT" W4[Worker 1
listen on :80] W5[Worker 2
listen on :80] W6[Worker 3
listen on :80] K[Kernel] -->|Load balances| W4 K -->|Load balances| W5 K -->|Load balances| W6 Note2[Per-worker accept queue
No thundering herd] end style W4 fill:#5cb85c style W5 fill:#5cb85c style W6 fill:#5cb85c

Why This Matters for System Design

Without SO_REUSEPORT, Nginx or Go HTTP servers must use a shared accept queue, causing all worker processes to wake on each connection (thundering herd). With SO_REUSEPORT, the kernel distributes connections directly to workers, improving CPU cache locality and reducing context switches.

L7: Application Layer Networking

Layer 7 operates at the application protocol level (HTTP, gRPC, etc.). It can parse headers, route based on URL paths, terminate SSL, and cache responses.

HTTP Parsing and Header Inspection

L7 proxies must parse the entire HTTP request to make routing decisions. This adds CPU cost and latency compared to L4.

sequenceDiagram participant C as Client participant L7 as L7 Proxy (Nginx, Envoy) participant B1 as Backend 1 /api participant B2 as Backend 2 /images C->>L7: GET /api/users HTTP/1.1
Host: example.com
Cookie: session=abc Note over L7: Parse HTTP headers
Extract: method, path, headers Note over L7: Routing decision based on path L7->>B1: GET /api/users HTTP/1.1
X-Forwarded-For: 1.2.3.4 B1->>L7: HTTP/1.1 200 OK
Content-Type: application/json Note over L7: Can inspect response
Cache, compress, log L7->>C: HTTP/1.1 200 OK
Content-Type: application/json

Real Numbers: L7 Parsing Overhead

  • HTTP parsing CPU cost: 5-20 microseconds per request (depends on header size)
  • Latency added: 0.1-1ms (buffering + parsing + routing decision)
  • Memory per connection: 4-16KB for request/response buffers
  • Throughput: Nginx ~100K req/sec per core, Envoy ~50K req/sec (more features)

SSL Termination at L7

L7 proxies can terminate TLS, decrypt traffic, inspect it, then re-encrypt to backends. This enables caching, compression, and WAF (Web Application Firewall) inspection.

graph LR C[Client] -->|TLS 1.3
Encrypted| L7[L7 Proxy] L7 -->|Decrypts| P[Plaintext
HTTP] P --> I1[Inspect headers] P --> I2[Cache check] P --> I3[WAF rules] P --> I4[Compress response] L7 -->|TLS to backend
or plaintext| B[Backend] style P fill:#f0ad4e style L7 fill:#5bc0de
SSL Termination Strategy Security Performance Use Case
Terminate at L7, plaintext to backend Low (plaintext in datacenter) High (single TLS handshake) Trusted datacenter, need L7 features
Terminate at L7, re-encrypt to backend High (end-to-end encryption) Medium (double TLS overhead) Compliance requires encryption everywhere
SSL passthrough (L4) High (proxy never sees plaintext) High (no parsing) Cannot inspect/cache/route by content

Content-Based Routing

L7 proxies route based on HTTP headers, URL paths, cookies, or even request body content.

graph TB C[Client Request] --> L7[L7 Proxy] L7 -->|Path: /api/*| API[API Servers] L7 -->|Path: /static/*| CDN[CDN/Object Storage] L7 -->|Header: X-Mobile: true| Mobile[Mobile Backend] L7 -->|Cookie: beta=true| Beta[Beta Canary] L7 -->|Default| Default[Default Backend] style L7 fill:#5bc0de

Interview Signal

Mentioning content-based routing shows depth: "We use Envoy to route /graphql to our GraphQL gateway and /rest/* to legacy services. We also inspect the X-API-Version header to gradually migrate traffic to v2 APIs without client changes."

L4 vs L7 Load Balancing: Deep Comparison

This is a critical system design decision. L4 is faster but less flexible; L7 is feature-rich but adds overhead.

Feature Comparison

graph TB C1[Client] -->|TCP connection| L4[L4 LB] L4 -->|Routes based on:ltbr/gtIP, port, TCP state| B1[Backend 1] L4 --> B2[Backend 2] L4 --> B3[Backend 3] Note1["Cannot see HTTP lt br/>Fast, low CPU"] C2[Client] -->|HTTP/HTTPS| L7[L7 LB] L7 --> B6[Cookie:session -> Sticky Backend] Note2["Full HTTP parsing More features, more CPU"] style L4 fill:#2374ab style L7 fill:#5bc0de
Aspect L4 Load Balancer L7 Load Balancer
Protocol Awareness None (sees TCP/UDP streams) Full HTTP/gRPC/WebSocket parsing
Routing Decisions IP, port, round-robin, least-connections URL path, headers, cookies, query params, request body
Session Affinity Source IP hashing (brittle with NAT) Cookie-based (reliable)
SSL Termination Passthrough only (cannot inspect) Can terminate, inspect, re-encrypt
Latency Added ~0.01-0.1ms (minimal) 0.5-2ms (buffering + parsing)
Throughput 10-100 Gbps per instance 1-10 Gbps per instance (CPU bound)
Caching Not possible Can cache HTTP responses
WAF / Rate Limiting Limited (TCP rate limits only) Full application-layer rules
Health Checks TCP connect or UDP packet HTTP GET /health with response validation
Cost Lower (less CPU, simpler) Higher (more CPU, more memory)

Direct Server Return (DSR)

With DSR, the load balancer forwards requests to backends, but backends respond directly to clients, bypassing the LB. This asymmetric routing avoids LB becoming a bottleneck for response traffic.

sequenceDiagram participant C as Client 1.2.3.4 participant LB as L4 Load Balancer 10.0.0.1 participant B as Backend 10.0.1.5 Note over LB: DSR Mode C->>LB: SYN to 10.0.0.1:80 LB->>B: Forward SYN, preserve src IP 1.2.3.4 Note over B: Backend has VIP 10.0.0.1
configured on loopback B->>C: SYN-ACK from 10.0.0.1 C->>B: ACK to 10.0.0.1 (routed directly to backend!) Note over C,B: All subsequent packets bypass LB C->>B: HTTP Request B->>C: HTTP Response (large!) Note over LB: LB only sees inbound SYN
Response traffic does NOT go through LB

Real Numbers: DSR Impact

  • Response traffic: 10-100x larger than request (HTML, images, JSON responses)
  • LB bandwidth savings: ~90% (only handles inbound requests, not responses)
  • Constraint: L4 only, requires backend to own VIP on loopback interface
  • Use case: Video streaming, large file downloads, high-bandwidth APIs

Why This Matters for System Design

If you're designing a video streaming service, DSR is critical. Without it, if your LB handles 10 Gbps inbound (requests), it must also handle 500 Gbps outbound (video streams), requiring massive LB capacity. With DSR, the LB only sees 10 Gbps.

Scenario Use L4 Use L7
Single backend service, need max throughput βœ“ (minimal overhead) βœ— (overkill)
Route /api vs /static to different services βœ— (cannot see URL) βœ“ (content routing)
Need SSL inspection for security βœ— (passthrough only) βœ“ (terminate SSL)
Sticky sessions based on cookies βœ— (source IP unreliable) βœ“ (cookie affinity)
Database connection pooling βœ“ (simple TCP proxy) βœ— (not HTTP)
Canary deployments (1% to new version) βœ— (random) βœ“ (header-based routing)
Video streaming (large responses) βœ“ (DSR for bandwidth) βœ— (LB bottleneck)

Connection Pooling

Connection pooling reuses TCP connections instead of opening a new one for each request. This avoids the latency cost of TCP + TLS handshakes.

Why Pool Connections?

sequenceDiagram participant A as App participant DB as Database Note over A,DB: Without Connection Pooling (per-request connection) A->>DB: TCP SYN DB->>A: SYN-ACK A->>DB: ACK (1.5 RTT) A->>DB: TLS ClientHello DB->>A: TLS ServerHello A->>DB: TLS Finished (2 RTT for TLS 1.3) A->>DB: SQL Query DB->>A: Result A->>DB: FIN (close connection) Note over A,DB: Total: 3.5 RTT + query time
sequenceDiagram participant A as App participant P as Connection Pool participant DB as Database Note over A,DB: With Connection Pooling Note over P: Pool initialized with 10 connections A->>P: Request connection P->>A: Return existing connection (0 RTT) A->>DB: SQL Query DB->>A: Result A->>P: Return connection to pool Note over P: Connection remains open for reuse Note over A,DB: Total: 0 RTT + query time

Real Numbers: Connection Setup Cost

  • TCP handshake: 1.5 RTT
  • TLS 1.3 handshake: 1 RTT (TLS 1.2: 2 RTT)
  • Total setup: 2.5 RTT (TLS 1.3) or 3.5 RTT (TLS 1.2)
  • Cross-region RTT: 50-200ms
  • Setup cost without pooling: 125-700ms per request!

Pool Sizing Formulas

Pool size is a critical tuning parameter. Too small: requests wait for connections. Too large: memory waste, backend overload.

HikariCP Formula (Database Pools)

connections = ((core_count * 2) + effective_spindle_count)

For SSDs (effectively infinite spindles): connections = core_count * 2

Example: 8-core database server β†’ pool size = 16 connections per app instance

HTTP Connection Pool Sizing

pool_size = (requests_per_second / requests_per_connection_per_second) * safety_factor

Example: 1000 RPS, each connection handles 100 req/sec (10ms per request) β†’ 10 connections * 2 (safety) = 20

Pool Size Pros Cons
Too Small (e.g., 1-2) Low backend load High latency (queueing), poor throughput
Optimal (10-50) Low latency, good throughput, efficient resource use Requires tuning based on workload
Too Large (100+) No queueing Memory waste, backend overload, connection thrashing

HTTP Connection Pools

HTTP clients (curl, requests, httpx, OkHttp) maintain connection pools per destination host.

graph TB subgraph "Application" T1[Thread 1] T2[Thread 2] T3[Thread 3] end subgraph "HTTP Client Connection Pool" P[Pool Manager] C1[Connection 1
to api.example.com] C2[Connection 2
to api.example.com] C3[Connection 3
to api.example.com] C4[Connection 4
to db.example.com] end T1 -->|Request| P T2 -->|Request| P T3 -->|Request| P P -->|Checkout| C1 P -->|Checkout| C2 P -->|Checkout| C3 P -->|Checkout| C4 C1 -->|HTTP Request| S1[api.example.com] C2 -->|HTTP Request| S1 C3 -->|HTTP Request| S1 C4 -->|HTTP Request| S2[db.example.com] style P fill:#5bc0de
# Python httpx with connection pooling
import httpx

# Pool with 100 max connections, 10 per host
client = httpx.Client(
    limits=httpx.Limits(max_connections=100, max_keepalive_connections=10)
)

# Reuses connections automatically
response1 = client.get("https://api.example.com/users")
response2 = client.get("https://api.example.com/posts")  # Same connection!

Database Connection Pools

Database connections are expensive (authentication, session state). Pools are essential for databases.

graph TB subgraph "Application Instances" A1[App Instance 1
Pool: 10 connections] A2[App Instance 2
Pool: 10 connections] A3[App Instance 3
Pool: 10 connections] end subgraph "Connection Pooler (PgBouncer)" PG[PgBouncer
Pool: 50 connections] end subgraph "Database" DB[(PostgreSQL
Max connections: 100)] end A1 -->|10 connections| PG A2 -->|10 connections| PG A3 -->|10 connections| PG PG -->|50 connections| DB Note1[3 app instances * 10 = 30 connections
PgBouncer pools to 50
Database only sees 50, not 30] style PG fill:#f0ad4e style DB fill:#5cb85c
Tool Database Pooling Mode Use Case
HikariCP Any JDBC (MySQL, Postgres, Oracle) Application-level (per JVM) Java/Kotlin apps
PgBouncer PostgreSQL Session, transaction, or statement Shared pool across many apps
ProxySQL MySQL Transaction-level MySQL pooling + query routing
SQLAlchemy Any (Python DB-API) Application-level Python apps

Failure Mode: Connection Pool Exhaustion

Symptom: Requests timeout waiting for a connection from the pool.

Diagnosis: Pool metrics show all connections checked out, queue depth growing.

Causes: Slow queries (hold connections longer), insufficient pool size, connection leaks (not returned).

Fix: Increase pool size, optimize queries, fix connection leaks, add connection timeout.

Interview Signal

Discussing connection pooling depth: "We size our database connection pool based on the backend's core count, not the number of app instances. With 50 app instances and a pool of 10 each, we'd have 500 connections, overwhelming our 16-core Postgres. We use PgBouncer in transaction mode to multiplex 500 app connections down to 32 database connections."

Keep-Alive: HTTP vs TCP

Keep-alive has two meanings: HTTP keep-alive (connection reuse) and TCP keepalive (dead connection detection). They're often confused!

HTTP Keep-Alive (Connection Reuse)

HTTP/1.0 closed connections after each request. HTTP/1.1 defaults to persistent connections (Connection: keep-alive), allowing multiple requests per TCP connection.

sequenceDiagram participant C as Client participant S as Server Note over C,S: HTTP/1.0 (no keep-alive) C->>S: GET /page1.html (Connection: close) S->>C: 200 OK Note over C,S: Connection closed C->>S: TCP handshake C->>S: GET /style.css (Connection: close) S->>C: 200 OK Note over C,S: Connection closed C->>S: TCP handshake C->>S: GET /script.js (Connection: close) S->>C: 200 OK Note over C,S: Connection closed Note over C,S: 3 requests = 3 TCP handshakes!
sequenceDiagram participant C as Client participant S as Server Note over C,S: HTTP/1.1 (keep-alive default) C->>S: TCP handshake C->>S: GET /page1.html (Connection: keep-alive) S->>C: 200 OK (Connection: keep-alive) Note over C,S: Connection stays open C->>S: GET /style.css (reuse connection) S->>C: 200 OK C->>S: GET /script.js (reuse connection) S->>C: 200 OK Note over C,S: 3 requests = 1 TCP handshake!

Real Numbers: Keep-Alive Impact

  • TCP handshake cost: 1.5 RTT (e.g., 15ms for 10ms RTT)
  • Typical web page: 50-100 resources (HTML, CSS, JS, images)
  • Without keep-alive: 50 * 15ms = 750ms just for handshakes
  • With keep-alive: 1 * 15ms = 15ms
  • Speedup: 50x reduction in handshake overhead

TCP Keepalive (Connection Liveness Probe)

TCP keepalive is a mechanism to detect dead connections. After a connection is idle for tcp_keepalive_time, the kernel sends a probe packet. If no response after multiple probes, the connection is closed.

sequenceDiagram participant C as Client participant S as Server Note over C,S: Connection established Note over C,S: Idle for 2 hours (tcp_keepalive_time=7200s) C->>S: TCP Keepalive Probe S->>C: ACK (connection alive) Note over C,S: Continue idling Note over C,S: Idle for another 2 hours C->>S: TCP Keepalive Probe Note over S: Server crashed, no response Note over C: Wait tcp_keepalive_intvl=75s C->>S: TCP Keepalive Probe (retry 1) Note over C: No response Note over C: Wait 75s C->>S: TCP Keepalive Probe (retry 2) Note over C: No response Note over C: After tcp_keepalive_probes=9 failures Note over C: Close connection (dead peer)
Aspect HTTP Keep-Alive TCP Keepalive
Layer Application (L7) Transport (L4)
Purpose Reuse connection for multiple requests Detect dead connections
Default Enabled in HTTP/1.1 Disabled by default (opt-in per socket)
Timeout 5-120 seconds (server config) 2 hours (Linux default, very long!)
Closes connection if Idle timeout exceeded, max requests reached, client sends Connection: close No response to keepalive probes
Use Case Web page loading (many resources) Long-lived connections (database, SSH, WebSocket)

HTTP Keep-Alive Timeout Tuning

Servers close idle keep-alive connections after a timeout. Too short: clients must reconnect frequently. Too long: server holds many idle connections.

Real Numbers: Typical Keep-Alive Settings

  • Nginx default: 75 seconds timeout, unlimited requests
  • Apache default: 5 seconds timeout, 100 requests per connection
  • AWS ALB: 60 seconds idle timeout
  • CloudFlare: 90 seconds timeout
Timeout Value Pros Cons
Short (5-10s) Frees resources quickly, low memory Clients reconnect often, negating keep-alive benefit
Medium (60-120s) Good balance for web traffic Standard choice
Long (300s+) Minimize reconnections for chatty clients Many idle connections consume memory/file descriptors
Very Long (hours) Suitable for WebSocket, SSE Must use TCP keepalive to detect dead connections

Failure Mode: Keep-Alive Timeout Mismatch

Symptom: Clients get intermittent "connection reset" errors.

Cause: Server closes connection due to idle timeout, but client doesn't know and sends request on closed connection.

Fix: Ensure client connection pool idle timeout < server keep-alive timeout. Example: server timeout = 60s, client pool timeout = 50s.

Head-of-Line (HOL) Blocking

HOL blocking occurs when one slow/lost packet blocks all subsequent packets, even if they're independent. This manifests at multiple layers.

TCP Head-of-Line Blocking

TCP guarantees in-order delivery. If packet N is lost, packets N+1, N+2, ... are buffered (not delivered to application) until N is retransmitted.

sequenceDiagram participant S as Sender participant N as Network participant R as Receiver S->>N: Packet 1 (seq=1) S->>N: Packet 2 (seq=2) S->>N: Packet 3 (seq=3) - LOST S->>N: Packet 4 (seq=4) S->>N: Packet 5 (seq=5) N->>R: Packet 1 arrives Note over R: Deliver to app immediately N->>R: Packet 2 arrives Note over R: Deliver to app immediately N->>R: Packet 4 arrives (out of order!) Note over R: Buffer, wait for packet 3 N->>R: Packet 5 arrives (out of order!) Note over R: Buffer, wait for packet 3 Note over S: Timeout, retransmit packet 3 S->>N: Packet 3 (retransmission) N->>R: Packet 3 arrives Note over R: NOW deliver packets 3, 4, 5 to app Note over R: HOL blocking: packets 4, 5 waited for packet 3

Real Numbers: TCP HOL Blocking Impact

  • Packet loss rate: 0.1-1% typical on Internet
  • Retransmission timeout: 200-1000ms (RTO)
  • HOL delay: All queued packets wait for retransmission RTT
  • Example: 1% loss, 100ms RTT β†’ 1 in 100 packets blocked for 100ms

HTTP/1.1 Head-of-Line Blocking

HTTP/1.1 sends requests serially over a connection. Request 2 cannot be sent until response 1 is received, even if they're independent.

sequenceDiagram participant C as Client participant S as Server Note over C,S: HTTP/1.1 (pipelined, but rare in practice) C->>S: GET /small.json C->>S: GET /large.jpg (10MB) C->>S: GET /tiny.css S->>C: Response 1: small.json (fast) Note over S: Processing large.jpg (slow) S->>C: Response 2: large.jpg (BLOCKS response 3) S->>C: Response 3: tiny.css (must wait!) Note over C,S: Response 3 blocked by response 2
even though it finished first

Why This Matters for System Design

Browsers work around HTTP/1.1 HOL blocking by opening 6-8 parallel connections per domain. This multiplies connection overhead and doesn't scale. HTTP/2 solves this with multiplexing.

HTTP/2 Stream Multiplexing (Solves L7 HOL, Not L4)

HTTP/2 multiplexes multiple streams over a single TCP connection. Each stream is independent, so one slow response doesn't block others.

graph TB C[Client] -->|Stream 1: GET /small.json| S1[Server] C -->|Stream 3: GET /large.jpg| S1 C -->|Stream 5: GET /tiny.css| S1 Note1["Responses can arrive out of order Stream 5 completes before Stream 3"] style S1 fill:#5cb85c

However, HTTP/2 still suffers from TCP-level HOL blocking. If one TCP packet is lost, all streams on that connection wait for retransmission.

sequenceDiagram participant C as Client (HTTP/2) participant TCP as TCP Layer participant S as Server (HTTP/2) Note over C,S: 3 HTTP/2 streams multiplexed C->>TCP: Stream 1 data (in TCP packet 1) C->>TCP: Stream 2 data (in TCP packet 2) C->>TCP: Stream 3 data (in TCP packet 3 - LOST) C->>TCP: Stream 1 data (in TCP packet 4) TCP->>S: Packet 1 arrives Note over S: Deliver Stream 1 data TCP->>S: Packet 2 arrives Note over S: Deliver Stream 2 data TCP->>S: Packet 4 arrives (out of order!) Note over TCP,S: TCP buffers, waits for packet 3 Note over TCP: ALL streams blocked! Note over C: Retransmit packet 3 C->>TCP: Packet 3 (retransmission) TCP->>S: Packet 3 arrives Note over S: NOW deliver Stream 3 and queued Stream 1 data

HTTP/3 & QUIC: True Multiplexing Without HOL Blocking

QUIC (HTTP/3) runs over UDP and implements per-stream reliability. Loss on one stream doesn't block others.

graph TB subgraph "HTTP/3 / QUIC (Over UDP)" C[Client] -->|Stream 1| Q[QUIC Layer] C -->|Stream 2| Q C -->|Stream 3| Q Q -->|UDP packets| N[Network] N -->|Packet for Stream 3 lost| Q2[QUIC Layer Server] Q2 -->|Stream 1 data delivered| S[Server] Q2 -->|Stream 2 data delivered| S Q2 -->|Stream 3 waits for retransmit| S Note1[Stream 1 and 2 NOT blocked by Stream 3 loss] end style Q fill:#5cb85c style Q2 fill:#5cb85c
Protocol L7 HOL Blocking L4 (TCP) HOL Blocking Best For
HTTP/1.1 Yes (serial requests) Yes (TCP) Simple, legacy compatibility
HTTP/2 over TCP No (multiplexed streams) Yes (TCP) Low-latency networks, HTTP/3 not available
HTTP/3 over QUIC No (multiplexed streams) No (UDP, per-stream reliability) High-latency, lossy networks (mobile, WiFi)

Real Numbers: HTTP/3 Performance Gain

  • 1% packet loss, HTTP/2: 20-30% throughput degradation (TCP HOL blocking)
  • 1% packet loss, HTTP/3: 5-10% degradation (only affected streams wait)
  • Benefit multiplier: 2-3x better throughput under loss with HTTP/3
  • Mobile networks: 2-5% packet loss common β†’ HTTP/3 significantly better

Interview Signal

Explaining HOL blocking depth: "We migrated our mobile app from HTTP/2 to HTTP/3 because mobile networks have 2-3% packet loss. With HTTP/2, one lost packet blocked all streams on that connection, causing tail latencies to spike. HTTP/3's per-stream retransmission means one slow image load doesn't block the API response."

Connection Management at Scale

At scale, connection limits become bottlenecks: file descriptors, ephemeral ports, connection tracking tables.

File Descriptor Limits

On Unix systems, each socket is a file descriptor. Processes have per-process and system-wide limits.

# Check per-process limit
ulimit -n
# Output: 1024 (default on many systems)

# Check system-wide limit
cat /proc/sys/fs/file-max
# Output: 1048576 (example)

# Increase per-process limit (requires root or capability)
ulimit -n 65536

# Permanent change: edit /etc/security/limits.conf
* soft nofile 65536
* hard nofile 65536

Failure Mode: File Descriptor Exhaustion

Symptom: "Too many open files" error, cannot accept new connections.

Diagnosis: lsof -p PID | wc -l shows file descriptor count approaching limit.

Causes: Connection leaks (not closing sockets), insufficient ulimit, file handle leaks.

Fix: Increase ulimit, fix leaks, use connection pooling with max size.

Ephemeral Port Exhaustion

Clients use ephemeral ports (32768-60999 on Linux) for outbound connections. With TIME_WAIT, ports are held for 60s after close.

graph TB C[Client
IP: 10.0.0.5] -->|Port 32768| S[Server
IP: 10.0.1.10:80] C -->|Port 32769| S C -->|Port 32770| S C -->|...| S C -->|Port 60999| S Note1[28,231 possible connections
If each lasts 60s TIME_WAIT
Max sustained: 470 conn/sec] style C fill:#d9534f

Real Numbers: Ephemeral Port Math

  • Default range: 32768-60999 (28,232 ports)
  • TIME_WAIT duration: 60 seconds
  • Max sustained connections: 28,232 / 60 = ~470 conn/sec per client IP
  • To sustain 10K conn/sec: Need 22 client IPs or connection reuse
Solution How It Works Tradeoffs
Increase ephemeral port range sysctl net.ipv4.ip_local_port_range="10000 65535" 55K ports instead of 28K, but still limited
Enable tcp_tw_reuse Reuse TIME_WAIT ports for outbound connections Safe for clients, don't use on servers
Connection pooling / keep-alive Reuse connections, avoid TIME_WAIT accumulation Best solution, but requires client support
Multiple client IPs Each IP has independent ephemeral port range Requires additional IPs, complex routing

Connection Tracking Tables (conntrack)

Linux stateful firewalls (iptables with connection tracking) maintain a table of all connections. This table has a maximum size.

# Check current conntrack entries
cat /proc/sys/net/netfilter/nf_conntrack_count
# Output: 12543

# Check max conntrack entries
cat /proc/sys/net/netfilter/nf_conntrack_max
# Output: 65536

# Increase limit (requires root)
sysctl -w net.netfilter.nf_conntrack_max=262144

Failure Mode: Conntrack Table Full

Symptom: New connections fail with "nf_conntrack: table full, dropping packet" in dmesg.

Diagnosis: cat /proc/sys/net/netfilter/nf_conntrack_count equals nf_conntrack_max.

Causes: High connection rate, long-lived connections, DDoS attack.

Fix: Increase nf_conntrack_max, reduce timeout values, disable conntrack if not needed.

TIME_WAIT Accumulation at Scale

High-traffic servers accumulate many TIME_WAIT connections. This is usually harmless (they consume minimal memory), but can be confusing.

# Count connections in each state
netstat -an | awk '{print $6}' | sort | uniq -c | sort -rn
# Output:
#   45123 TIME_WAIT
#    8765 ESTABLISHED
#     234 LISTEN
#      12 FIN_WAIT1

Why This Matters for System Design

Seeing 100K TIME_WAIT connections is normal for a busy web server. They consume ~4KB each (socket structure), so 100K = 400MB. Modern servers have plenty of memory. The real concern is ephemeral port exhaustion on clients or conntrack table size on firewalls.

Network Protocols for Microservices

Microservices use specialized protocols optimized for service-to-service communication: binary encoding, multiplexing, schema enforcement.

gRPC: HTTP/2-Based RPC

gRPC uses HTTP/2 for transport, Protocol Buffers for serialization, and supports streaming. It's designed for low-latency, high-throughput microservices.

graph TB subgraph "gRPC Stack" A[Application Code] -->|Generated stubs| G[gRPC Library] G -->|Serialize| P[Protocol Buffers] P -->|Binary frames| H[HTTP/2] H -->|Multiplexed streams| T[TCP] end T --> N[Network] style G fill:#5cb85c style P fill:#f0ad4e
Feature gRPC REST/JSON over HTTP/1.1
Encoding Binary (Protobuf, ~3-10x smaller) Text (JSON)
Schema Enforced (Protobuf definitions) Optional (OpenAPI/Swagger)
Streaming Bidirectional, server, client streaming Limited (chunked encoding, SSE)
Multiplexing Yes (HTTP/2) No (serial requests)
Latency Lower (binary, less parsing) Higher (text parsing, no multiplexing)
Browser Support Limited (needs gRPC-Web proxy) Native
Use Case Microservice-to-microservice Browser-to-server, public APIs

Real Numbers: gRPC Performance

  • Protobuf vs JSON size: 3-10x smaller (depends on schema)
  • Serialization speed: Protobuf ~5-10x faster than JSON
  • Latency improvement: 20-50% lower than REST/JSON (less data, faster parsing)
  • Connection overhead: HTTP/2 multiplexing reduces connection count by 10-100x

Apache Thrift: Facebook's RPC Framework

Thrift is similar to gRPC: schema-defined, binary encoding, cross-language. Predates gRPC (2007 vs 2015), widely used at Facebook/Meta.

Aspect gRPC Apache Thrift
Transport HTTP/2 (fixed) Pluggable (HTTP, TCP, framed)
Serialization Protobuf Binary, Compact, JSON protocols
Streaming Bidirectional (HTTP/2) Limited (transport dependent)
Ecosystem Google, CNCF, growing Meta, mature, stable

Protocol Buffers Wire Format

Protobuf uses variable-length encoding and field tags for efficient binary serialization.

// Protobuf schema
message User {
  int32 id = 1;
  string name = 2;
  repeated string emails = 3;
}

// Example: User{id: 123, name: "Alice", emails: ["a@example.com"]}
// Wire format (hex):
// 08 7B       - field 1 (id), varint, value 123
// 12 05 41 6C 69 63 65  - field 2 (name), length-delimited, "Alice"
// 1A 0F 61 40 65 78 61 6D 70 6C 65 2E 63 6F 6D  - field 3 (emails[0]), "a@example.com"
// Total: 29 bytes

// Equivalent JSON:
// {"id":123,"name":"Alice","emails":["a@example.com"]}
// Total: 52 bytes (1.8x larger)

Why This Matters for System Design

Protobuf's efficiency compounds at scale. If your microservices exchange 1 TB/day of data, switching from JSON to Protobuf saves ~500 GB of network traffic. At $0.01/GB for data transfer, that's $5/day = $1,825/year savings, plus reduced latency.

Service Mesh Networking

Service meshes (Istio, Linkerd, Consul Connect) add a networking layer between microservices, providing observability, security, and traffic control without changing application code.

Sidecar Proxy Model (Envoy)

Each service pod has a sidecar proxy (Envoy) that intercepts all inbound/outbound traffic. The proxy enforces policies, collects metrics, and handles retries.

graph TB subgraph "Service A Pod" A[Service A
localhost:8080] -->|Outbound| EA[Envoy Proxy
Sidecar] end subgraph "Service B Pod" EB[Envoy Proxy
Sidecar] -->|Inbound| B[Service B
localhost:8080] end EA -->|mTLS
Circuit breaking
Retries
Metrics| EB Note1[Application sees localhost
Envoy handles all networking complexity] style EA fill:#5bc0de style EB fill:#5bc0de

Why This Matters for System Design

Without a service mesh, every microservice must implement retries, timeouts, circuit breaking, metrics, and mTLS in application code. With a mesh, these are declarative config in the proxy. When you have 50 microservices in 5 languages, this is a massive win for consistency.

Traffic Routing in Service Mesh

Service mesh proxies route traffic based on rules: canary deployments, A/B tests, fault injection.

graph TB C[Client] -->|Request| E[Envoy Proxy] E -->|90%| V1[Service v1] E -->|10% canary| V2[Service v2] E -->|Header: user=beta| Beta[Beta Backend] E -->|Fault injection: 1% 500 error| Chaos[Chaos Testing] style E fill:#5bc0de style V2 fill:#f0ad4e style Beta fill:#5cb85c

Circuit Breaking at Network Level

Envoy can circuit-break at the connection level: if backend has too many connections or errors, stop sending traffic.

stateDiagram-v2 [*] --> Closed: Normal operation Closed --> Open: Consecutive errors > threshold Open --> HalfOpen: After timeout HalfOpen --> Closed: Probe succeeds HalfOpen --> Open: Probe fails note right of Closed: Forward all requests, Track error rate note right of Open: Fail fast (no backend call), Return 503 immediately note right of HalfOpen: Allow 1 probe request, If success resume traffic
Circuit Breaker Config Meaning Typical Value
Max connections Total connections to backend 1024
Max pending requests Requests queued waiting for connection 1024
Max requests per connection HTTP/2 streams per connection 100
Consecutive errors Errors before opening circuit 5
Interval Time window for error counting 10s

Mutual TLS (mTLS) Between Services

Service mesh enables zero-trust networking: every service-to-service call uses mTLS for authentication and encryption.

sequenceDiagram participant A as Service A (Envoy) participant B as Service B (Envoy) Note over A,B: mTLS Handshake A->>B: ClientHello + Client Certificate B->>A: ServerHello + Server Certificate Note over B: Verify client cert signed by trusted CA Note over A: Verify server cert signed by trusted CA A->>B: Encrypted Request Note over A,B: Identity verified
Traffic encrypted B->>A: Encrypted Response

Real Numbers: Service Mesh Overhead

  • Latency added: 0.5-2ms per hop (sidecar proxy processing)
  • CPU overhead: 5-15% per pod (Envoy proxy)
  • Memory overhead: 50-200MB per pod (Envoy)
  • Benefit: Consistent observability, security, resilience without code changes

Interview Signal

Discussing service mesh tradeoffs: "We adopted Istio because we had 30 microservices implementing retries inconsistently. Some had exponential backoff, others linear, some had no retries. Istio gave us a single place to configure retries, timeouts, and circuit breaking. The 1-2ms latency cost is worth it for operational simplicity."

Network Debugging

When systems fail, network issues are often the culprit. Knowing how to diagnose connection problems is essential.

tcpdump & Wireshark Patterns

tcpdump captures raw packets. Wireshark provides GUI analysis. Key patterns to recognize:

# Capture packets on interface eth0, port 80
tcpdump -i eth0 -n port 80

# Capture full packet content (not just headers)
tcpdump -i eth0 -n -A port 80

# Save to file for Wireshark analysis
tcpdump -i eth0 -w capture.pcap port 80

# Common patterns:

# 1. SYN retransmissions (server not responding)
# Client sends SYN, no SYN-ACK, client retries
12:00:00.000 IP 1.2.3.4.5678 > 10.0.0.5.80: Flags [S], seq 123
12:00:01.000 IP 1.2.3.4.5678 > 10.0.0.5.80: Flags [S], seq 123  # Retransmit!
12:00:03.000 IP 1.2.3.4.5678 > 10.0.0.5.80: Flags [S], seq 123  # Retransmit!

# 2. RST from server (connection refused)
12:00:00.000 IP 1.2.3.4.5678 > 10.0.0.5.80: Flags [S], seq 123
12:00:00.001 IP 10.0.0.5.80 > 1.2.3.4.5678: Flags [R], seq 0  # Reset!

# 3. Window size 0 (receiver buffer full, flow control)
12:00:00.000 IP 10.0.0.5.80 > 1.2.3.4.5678: Flags [.], ack 500, win 0
# Sender must wait for window update

# 4. Duplicate ACKs (packet loss, triggers fast retransmit)
12:00:00.000 IP 1.2.3.4.5678 > 10.0.0.5.80: Flags [.], ack 100
12:00:00.001 IP 1.2.3.4.5678 > 10.0.0.5.80: Flags [.], ack 100  # Duplicate
12:00:00.002 IP 1.2.3.4.5678 > 10.0.0.5.80: Flags [.], ack 100  # Duplicate
12:00:00.003 IP 1.2.3.4.5678 > 10.0.0.5.80: Flags [.], ack 100  # 3rd duplicate = retransmit

Network Partitions: How They Look

A network partition splits a cluster into two groups that cannot communicate. Each group may think the other is dead.

graph TB subgraph "Datacenter A" N1[Node 1
Leader] N2[Node 2] end subgraph "Datacenter B" N3[Node 3] N4[Node 4] end N1 -.->|Partition!
No connectivity| N3 N1 -.->|Partition!| N4 N2 -.->|Partition!| N3 N2 -.->|Partition!| N4 N1 --> N2 N3 --> N4 Note1[Datacenter A can't reach B
But A nodes can talk to each other
B nodes can talk to each other] style N1 fill:#d9534f style N3 fill:#d9534f

Failure Mode: Split-Brain from Network Partition

Symptom: Two leaders elected, conflicting writes, data inconsistency.

Cause: Network partition, no quorum enforcement.

Detection: Logs show "lost connection to N nodes", multiple nodes claiming to be leader.

Prevention: Use quorum-based consensus (Raft, Paxos), require majority for leader election.

Split-Brain Scenarios

Split-brain occurs when a cluster partitions and each side elects a leader, causing divergent state.

sequenceDiagram participant A as Node A (Datacenter 1) participant B as Node B (Datacenter 1) participant C as Node C (Datacenter 2) Note over A,C: Healthy cluster, A is leader A->>B: Heartbeat A->>C: Heartbeat Note over A,C: Network partition! A->>C: Heartbeat (dropped) Note over C: No heartbeat, election timeout C->>C: Elect self as leader (no quorum!) Note over A: Still thinks it's leader Note over C: Also thinks it's leader A->>A: Write X=1 C->>C: Write X=2 Note over A,C: Split-brain!
Two leaders, divergent state
Mitigation Strategy How It Works Tradeoff
Quorum (majority) Require N/2 + 1 nodes to elect leader Can't make progress if majority partitioned
Fencing (STONITH) Kill old leader if new one elected Requires reliable kill mechanism
Lease-based leadership Leader lease expires, step down if can't renew Brief unavailability during lease expiry
Witness node Odd-numbered tie-breaker in 3rd datacenter Requires 3rd location

Interview Signal

Explaining split-brain prevention: "Our database cluster uses Raft with 5 nodes across 3 datacenters. Even if one datacenter partitions (2 nodes lost), the remaining 3 form a quorum and continue serving writes. The partitioned nodes step down because they can't reach a majority. When the network heals, they rejoin as followers and replicate the leader's state."

Bandwidth vs Latency vs Throughput

These terms are often confused. Understanding the distinctions is critical for capacity planning.

Definitions

Term Definition Unit Analogy
Latency Time for a packet to travel from source to destination Milliseconds (ms) Highway travel time (A to B)
Bandwidth Maximum data transfer rate of a link Bits per second (bps, Gbps) Highway width (lanes)
Throughput Actual data transfer rate achieved Bits per second (bps, Gbps) Cars actually passing per second

Key Insight: Latency vs Bandwidth

You can have high bandwidth but high latency (e.g., satellite: 50 Mbps but 600ms latency). Or low bandwidth but low latency (e.g., LAN: 100 Mbps but 0.5ms latency). For interactive apps, latency matters more. For bulk transfers, bandwidth matters more.

Bandwidth-Delay Product (BDP)

BDP is the amount of data "in flight" on a network link. It determines optimal TCP buffer size.

Formula: Bandwidth-Delay Product

BDP = Bandwidth (bps) Γ— Round-Trip Time (seconds)

Example: 1 Gbps bandwidth, 100ms RTT

BDP = 1,000,000,000 bps Γ— 0.1 s = 100,000,000 bits = 12.5 MB

Meaning: To fully utilize the link, TCP must have 12.5 MB of unacknowledged data in flight.

graph LR S[Sender] -->|Bandwidth: 1 Gbps| N[Network
RTT: 100ms] N --> R[Receiver] Note1[BDP = 1 Gbps Γ— 100ms = 12.5 MB
TCP buffer must be >= 12.5 MB
to keep link full] style N fill:#5bc0de
Scenario Bandwidth RTT BDP Required TCP Buffer
LAN 1 Gbps 1 ms 125 KB ~256 KB
Cross-region 1 Gbps 50 ms 6.25 MB ~8 MB
Intercontinental 1 Gbps 200 ms 25 MB ~32 MB
Satellite 50 Mbps 600 ms 3.75 MB ~4 MB

Buffer Bloat: Too Much Buffering

Bufferbloat occurs when network devices have excessively large buffers, increasing latency under load.

graph TB S[Sender
Sending fast] -->|1 Gbps| R[Router
Large buffer: 1 second] R -->|100 Mbps link| D[Destination] Note1[Router buffer fills up
Packets wait 1 second in queue
Latency spikes from 10ms to 1000ms!] style R fill:#d9534f

Real Numbers: Buffer Bloat Impact

  • Symptom: Latency spikes during bulk transfers (downloads, uploads)
  • Cause: Router buffers sized for 100ms-1s of traffic
  • Fix: Active Queue Management (AQM) algorithms like CoDel, fq_codel
  • Result: Latency under load drops from 500ms to 20ms

Interview Signal

Discussing bandwidth-delay product: "For our CDN serving intercontinental traffic, we increased TCP buffer sizes from 4MB to 32MB. Our bandwidth is 10 Gbps with 200ms RTT to Asia, so BDP is 250 MB. Without large enough buffers, we couldn't fill the pipe, wasting bandwidth."

Data Center Networking

Modern datacenters use specialized topologies to provide high bandwidth, low latency, and fault tolerance for distributed systems.

Spine-Leaf Topology

Spine-leaf is the standard datacenter topology. Every leaf switch connects to every spine switch, providing equal-cost paths between any two servers.

graph TB subgraph "Spine Layer" S1[Spine 1] S2[Spine 2] S3[Spine 3] end subgraph "Leaf Layer" L1[Leaf 1] L2[Leaf 2] L3[Leaf 3] L4[Leaf 4] end subgraph "Servers" R1[Rack 1
20 servers] R2[Rack 2
20 servers] R3[Rack 3
20 servers] R4[Rack 4
20 servers] end L1 --> S1 L1 --> S2 L1 --> S3 L2 --> S1 L2 --> S2 L2 --> S3 L3 --> S1 L3 --> S2 L3 --> S3 L4 --> S1 L4 --> S2 L4 --> S3 L1 --> R1 L2 --> R2 L3 --> R3 L4 --> R4 style S1 fill:#2374ab style S2 fill:#2374ab style S3 fill:#2374ab style L1 fill:#5bc0de style L2 fill:#5bc0de style L3 fill:#5bc0de style L4 fill:#5bc0de

Why Spine-Leaf?

  • Predictable latency: Any server to any server is exactly 2 hops (leaf β†’ spine β†’ leaf)
  • High bandwidth: Multiple equal-cost paths (ECMP) for load balancing
  • Scalability: Add more spines or leaves without topology changes
  • Fault tolerance: Lose one spine, traffic reroutes to others

East-West vs North-South Traffic

graph TB I[Internet] -->|North-South
Client ↔ Server| Edge[Edge Routers] Edge --> Firewall[Firewall/LB] Firewall --> Web[Web Tier] Web -->|East-West
Server ↔ Server| App[App Tier] App -->|East-West| DB[DB Tier] Note1[North-South: 20%
East-West: 80%] style Web fill:#5bc0de style App fill:#5bc0de style DB fill:#5bc0de
Traffic Type Direction Typical % Characteristics
North-South Client ↔ Datacenter 20% Internet-facing, high latency, variable bandwidth, requires security
East-West Server ↔ Server in datacenter 80% Microservices, low latency, high bandwidth, trusted network

Why This Matters for System Design

Traditional 3-tier datacenter networks (core-aggregation-access) were designed for north-south traffic. Microservices shifted the majority to east-west. Spine-leaf topology optimizes for east-west with full bisection bandwidth: any leaf can send at full speed to any other leaf.

Equal-Cost Multi-Path (ECMP) Routing

ECMP load balances traffic across multiple equal-cost paths. In spine-leaf, a packet from Leaf 1 to Leaf 2 can go through Spine 1, 2, or 3.

graph TB L1[Leaf 1
Server A] -->|Flow 1| S1[Spine 1] L1 -->|Flow 2| S2[Spine 2] L1 -->|Flow 3| S3[Spine 3] S1 --> L2[Leaf 2
Server B] S2 --> L2 S3 --> L2 Note1[ECMP hashes packet 5-tuple
src IP, dst IP, src port, dst port, protocol
Same flow always uses same path] style S1 fill:#5cb85c style S2 fill:#5cb85c style S3 fill:#5cb85c

Real Numbers: ECMP Benefits

  • Paths: 3-16 spines typical (3-16x bandwidth multiplication)
  • Link utilization: 80-95% (vs 30-50% without ECMP)
  • Failure recovery: Automatic (routing protocol detects spine failure, redistributes flows)

BGP for Anycast: Same IP, Multiple Locations

Anycast announces the same IP prefix from multiple locations. Routers send traffic to the nearest location based on BGP routing.

graph TB C1[Client
New York] -->|Route to 1.2.3.4
Closest: US-East| R1[Router] C2[Client
London] -->|Route to 1.2.3.4
Closest: EU-West| R2[Router] R1 --> DC1[Datacenter US-East
Announces 1.2.3.4] R2 --> DC2[Datacenter EU-West
Announces 1.2.3.4] DC3[Datacenter Asia
Announces 1.2.3.4] Note1[Same IP address 1.2.3.4
BGP routes client to nearest datacenter] style DC1 fill:#5cb85c style DC2 fill:#5cb85c style DC3 fill:#5cb85c
Anycast Use Case How It Works Example
DNS 8.8.8.8 routed to nearest Google DNS server Google Public DNS, Cloudflare 1.1.1.1
CDN Edge PoPs announce same prefix, client routed to nearest Cloudflare, Fastly edge network
DDoS Mitigation Scrubbing centers announce victim IP during attack Cloudflare Magic Transit
Load Balancing Multiple servers announce same VIP, traffic distributed L4 load balancers in multiple datacenters

Interview Signal

Explaining anycast depth: "Our CDN uses anycast to route users to the nearest edge PoP. We announce our IP ranges from 100+ locations via BGP. A user in Tokyo connects to our Tokyo PoP, latency drops from 200ms (US) to 10ms. During a DDoS attack on Tokyo, we withdraw the BGP announcement, routing traffic to Osaka instead."

Interview Preparation: Networking Questions

Common FAANG interview questions and how to answer them with depth.

Question: Design a load balancer. L4 or L7?

Good Answer: "Depends on requirements. For a high-throughput, single-service backend (e.g., database proxy), I'd use L4 (TCP load balancing) for minimal latency and max throughput. If we need content-based routing (e.g., /api to service A, /static to CDN), then L7 (HTTP load balancing) is necessary despite the CPU cost. For very large responses (video streaming), I'd use L4 with DSR to avoid the LB becoming a bandwidth bottleneck."

Question: Your API has high latency. How do you investigate?

Good Answer: "First, add distributed tracing to measure time spent in each service hop. If latency is in network calls, check connection pooling: are we reusing connections or paying TCP+TLS handshake cost (3.5 RTT) per request? For cross-region calls, that's 150-200ms just for setup. Next, check if we're using HTTP/1.1 (serial requests) vs HTTP/2 (multiplexing). Use tcpdump to verify: look for repeated SYN packets (connection setup) vs reuse. Finally, measure BDP: if our TCP buffer is too small for high-latency links, we can't fill the pipe."

Question: How does HTTP/2 improve performance over HTTP/1.1?

Good Answer: "HTTP/2 adds stream multiplexing, header compression, and server push. Multiplexing eliminates HTTP/1.1 HOL blocking: multiple requests share one connection, so one slow response doesn't block others. This reduces connection count from 6-8 per domain to 1, saving handshake overhead. Header compression (HPACK) reduces header size by 85-90% for repeated headers. However, HTTP/2 still has TCP-level HOL blocking: if one TCP packet is lost, all streams wait for retransmission. HTTP/3 (QUIC) solves this with per-stream reliability over UDP."

Question: Your service is hitting connection limits. How do you scale?

Good Answer: "First, verify the bottleneck: file descriptors (ulimit -n), ephemeral ports (28K default range), or conntrack table. For file descriptors, increase ulimit. For ephemeral ports, enable tcp_tw_reuse (clients only) and use connection pooling to reuse connections instead of accumulating TIME_WAIT. If we're a client making many outbound connections, add more IP addresses (each has independent 64K port range). For connection tracking, increase nf_conntrack_max or disable conntrack if we're not doing stateful firewalling. Long-term, use connection pooling and HTTP keep-alive to minimize connection churn."

Question: Explain split-brain and how to prevent it.

Good Answer: "Split-brain occurs when a distributed system partitions into multiple groups, each electing a leader, causing divergent state. Example: 5-node cluster splits 2-3. Without quorum, both sides elect a leader and accept writes, creating conflicts. Prevention: require quorum (N/2 + 1) for leader election. In a 5-node cluster, need 3 votes. If network partitions 2-3, the 3-node side can elect a leader (has quorum), but the 2-node side cannot. Use consensus algorithms like Raft or Paxos which enforce quorum. Additionally, use lease-based leadership: leader must renew lease with majority; if it can't, it steps down, preventing dual leadership."

Signal Phrase What It Demonstrates
"TCP handshake is 1.5 RTT, TLS adds 1 RTT, so connection setup is 2.5 RTT" Understands network latency breakdown
"We use PgBouncer in transaction mode to multiplex 500 app connections to 32 database connections" Connection pooling at scale
"HTTP/2 solves L7 HOL but not L4; HTTP/3 solves both" Deep protocol understanding
"BDP is 12 MB for 1 Gbps / 100ms RTT, so we increased TCP buffer to 16 MB" Performance tuning depth
"We use spine-leaf topology for predictable 2-hop latency and ECMP for bandwidth" Datacenter networking knowledge
"Our service mesh adds 1ms latency but gives us mTLS, circuit breaking, and observability" Tradeoff awareness

Final Interview Tip

Always quantify. Don't say "connection pooling improves performance." Say "Without pooling, each request pays 150ms (cross-region TCP+TLS handshake). With pooling, that's 0ms. For 1000 RPS, we save 150,000ms = 2.5 minutes of cumulative latency per second, a 25x improvement in P99 latency."

TCP Congestion Control Algorithms Deep Dive

TCP congestion control prevents overwhelming the network. Different algorithms use different strategies to detect and respond to congestion, with massive performance differences in high-bandwidth or lossy networks.

TCP Reno: The Classic Algorithm

TCP Reno (1990) is the foundational congestion control algorithm. It uses packet loss as the primary signal of congestion.

Four Phases of Reno

Phase Growth Type Trigger Action
Slow Start Exponential Connection start or timeout Double cwnd every RTT (cwnd += 1 per ACK)
Fast ramp-up until ssthresh
Congestion Avoidance Linear (Additive Increase) cwnd >= ssthresh Increase cwnd by 1 MSS per RTT
Cautious growth to probe capacity
Fast Retransmit - 3 duplicate ACKs Retransmit missing packet immediately
Don't wait for timeout (1s+)
Fast Recovery Multiplicative Decrease After fast retransmit ssthresh = cwnd / 2
cwnd = ssthresh + 3
Stay in congestion avoidance (don't reset to slow start)
graph LR A[Slow Start
cwnd doubles
every RTT] -->|cwnd >= ssthresh| B[Congestion
Avoidance
cwnd += 1/RTT] B -->|3 dup ACKs| C[Fast
Retransmit] C --> D[Fast Recovery
cwnd = ssthresh/2 + 3] D --> B B -->|Timeout| A A -->|Timeout| A

AIMD Sawtooth Pattern

Reno's Additive Increase / Multiplicative Decrease (AIMD) creates a characteristic sawtooth graph:

graph TD A[cwnd over time] --> B["Exponential growth (slow start)"] B --> C["Linear growth (congestion avoidance)"] C --> D["Loss detected: cwnd cut in half"] D --> C

Problem: Reno treats all loss as congestion. In wireless/cellular networks with random loss (signal interference, not congestion), Reno unnecessarily reduces throughput.

CUBIC: High-BDP Optimization

CUBIC (2008) is the default algorithm in Linux since 2.6.19. It solves Reno's inefficiency in high-bandwidth delay product (BDP) networks.

Reno's Problem: Slow Recovery in High-BDP Networks

Example: 1 Gbps link, 100ms RTT β†’ BDP = 12.5 MB. If cwnd was 10,000 packets before loss, Reno cuts to 5,000 packets, then adds 1 packet/RTT. To recover to 10,000 packets takes 5,000 RTTs = 500 seconds (8+ minutes!).

CUBIC's Solution: Cubic Function Growth

Instead of linear growth, CUBIC uses a cubic function centered around the last congestion point:

W(t) = C Γ— (t - K)Β³ + Wmax

Where:
- W(t) = cwnd at time t after last congestion event
- C = scaling constant (0.4 by default)
- K = time to reach Wmax (when cubic function = 0)
- Wmax = cwnd when last congestion occurred
- t = elapsed time since last congestion

Growth Pattern

  • Just after loss: Fast growth (concave phase) to quickly recover
  • Near Wmax: Slow growth (inflection point) to cautiously probe if capacity increased
  • Beyond Wmax: Moderate growth (convex phase) to find new capacity
graph LR A[Loss at Wmax=10K] --> B[cwnd drops to 5K] B --> C[CUBIC: Fast concave growth
to 9K in 50 RTTs] C --> D[Slow growth near Wmax
cautious probing] D --> E[Convex growth beyond Wmax
find new capacity]

Result: CUBIC recovers much faster than Reno in high-BDP networks (data center, long-distance fiber). In the 1 Gbps / 100ms RTT example, CUBIC recovers in seconds instead of 8 minutes.

RTT Fairness

CUBIC's growth is time-based (not RTT-based like Reno). This improves fairness between flows with different RTTs sharing the same bottleneck.

BBR (Bottleneck Bandwidth and RTT): The Modern Approach

BBR (2016) from Google represents a paradigm shift: don't use packet loss as a congestion signal.

Why Loss is a Bad Signal

  • Random loss: Wireless networks have 1-5% loss from signal issues, not congestion
  • Bufferbloat: Large router buffers hide congestion until buffers fill and loss occurs β†’ high latency before correction
  • Reactive: Loss-based algorithms wait for congestion to occur, then react

BBR's Approach: Model-Based Control

BBR builds a model of the network path by continuously measuring:

  • BtlBw (Bottleneck Bandwidth): Maximum delivery rate observed in recent window (10 RTTs)
  • RTprop (Round-Trip Propagation time): Minimum RTT observed in recent window (10s)

Key Insight: Optimal operating point is cwnd = BtlBw Γ— RTprop (the BDP). At this point, the pipe is full but queues are not building.

Four BBR States

State Goal Action Duration
STARTUP Find bottleneck bandwidth Double sending rate each RTT (like slow start)
Stop when delivery rate stops increasing (3 consecutive rounds)
Initial connection (few RTTs)
DRAIN Drain queue built during STARTUP Send at BtlBw / 2.89 (geometric mean of 2x growth)
Reduces cwnd to match actual BDP
1 RTT
PROBE_BW Check if bandwidth increased Cycle pacing rate: 1.25Γ—, 1.0Γ—, 0.75Γ— every 8 RTTs
98% of connection lifetime spent here
Most of connection
PROBE_RTT Check if RTprop decreased If RTprop not updated in 10s, reduce cwnd to 4 packets for 200ms
Allows queues to fully drain to measure true propagation delay
200ms every 10s
stateDiagram-v2 [*] --> STARTUP STARTUP --> DRAIN: BtlBw found
(delivery rate stops growing) DRAIN --> PROBE_BW: Queue drained PROBE_BW --> PROBE_BW: Cycle pacing
(1.25x, 1x, 0.75x) PROBE_BW --> PROBE_RTT: RTprop not updated
in 10 seconds PROBE_RTT --> PROBE_BW: After 200ms

BBR Performance

Real-world results:

  • YouTube: 2-25x throughput improvement on mobile/satellite networks with 1-5% loss
  • Google B4 network: 2x throughput increase in wide-area data center links
  • Bufferbloat: Reduces latency by 10-100x by not filling buffers unnecessarily

Why BBR Handles Lossy Networks

Reno/CUBIC: 1% loss β†’ reduce cwnd by 50% every 100 packets β†’ throughput craters.

BBR: 1% loss β†’ ignore it, maintain cwnd = BtlBw Γ— RTprop β†’ full throughput.

BBR only reduces rate if RTT increases (queue building) or delivery rate decreases (true congestion), not on random loss.

Algorithm Comparison

Aspect Reno CUBIC BBR
Congestion Signal Packet loss Packet loss RTT increase + delivery rate
Growth Linear (AIMD) Cubic function Model-based (BDP target)
Loss Sensitivity High (50% cut per loss) High (30% cut per loss) Low (ignores random loss)
High-BDP Performance Poor (slow recovery) Good (fast recovery) Excellent (model-driven)
Bufferbloat Bad (fills buffers) Bad (fills buffers) Good (avoids filling buffers)
Fairness (RTT) Unfair (favors short RTT) Better (time-based growth) Good (model-based)
Adoption Legacy systems Linux default (most servers) Google services, YouTube, growing

Interview Tip: BBR as the Solution to Mobile Performance

When asked "how does YouTube maintain quality on lossy mobile networks?" the answer is BBR. Reno/CUBIC interpret packet loss as congestion and reduce throughput, but mobile networks have 1-5% random loss from signal interference, not congestion. BBR ignores loss and instead monitors RTT (queue buildup) and delivery rate (true congestion), achieving 2-25x throughput improvement on lossy networks.

BBR Fairness Concerns

BBR can be aggressive toward loss-based algorithms when sharing a bottleneck. If a CUBIC flow backs off due to loss, BBR (which ignores loss) claims that bandwidth. This is being addressed in BBRv2 (2020) with enhanced fairness mechanisms.

QUIC vs TCP HOL Blocking β€” Detailed

Head-of-line (HOL) blocking is a critical performance issue where one slow/lost item blocks unrelated items behind it. QUIC solves TCP's HOL blocking problem, which is why HTTP/3 over QUIC significantly outperforms HTTP/2 over TCP on lossy networks.

TCP HOL Blocking: The Problem

TCP guarantees in-order delivery within a single connection. If packet 5 is lost, packets 6-10 (even if received) are held in the TCP receive buffer and not delivered to the application until packet 5 is retransmitted and arrives.

HTTP/2 + TCP: Multiplexed Streams, Single TCP Connection

HTTP/2 multiplexes multiple HTTP requests (streams) over a single TCP connection. This reduces connection overhead (no repeated handshakes) but inherits TCP's HOL blocking:

sequenceDiagram participant Browser participant TCP as TCP Connection participant Server Note over Browser,Server: HTTP/2 multiplexes 3 streams on 1 TCP connection Browser->>TCP: Stream A: Request /image.jpg Browser->>TCP: Stream B: Request /style.css Browser->>TCP: Stream C: Request /script.js Server->>TCP: Stream A packet 1, 2, 3 Server->>TCP: Stream B packet 1, 2 Server->>TCP: Stream C packet 1 Note over TCP: Stream B packet 2 LOST in network TCP->>Browser: Stream A packet 1 βœ“ TCP->>Browser: Stream A packet 2 βœ“ Note over TCP: Stream A packet 3 RECEIVED
Stream B packet 1 RECEIVED
Stream C packet 1 RECEIVED
BUT BLOCKED by lost Stream B packet 2 Note over TCP: TCP waits for Stream B packet 2 retransmission Note over TCP: ALL STREAMS BLOCKED
even though Stream A and C data arrived Server->>TCP: Stream B packet 2 retransmitted (1 RTT later) TCP->>Browser: Stream A packet 3 βœ“ (finally delivered) TCP->>Browser: Stream B packet 1 βœ“ TCP->>Browser: Stream B packet 2 βœ“ TCP->>Browser: Stream C packet 1 βœ“

Key Issue: A single lost packet (Stream B packet 2) blocks ALL streams, even though Stream A packet 3 and Stream C packet 1 arrived successfully. This is TCP-level HOL blocking.

QUIC Solution: Independent Streams

QUIC is a transport protocol built on UDP (to avoid kernel TCP stack limitations). Each QUIC stream has independent flow control and delivery. A lost packet only blocks ITS stream.

HTTP/3 + QUIC: Same Scenario, No Blocking

sequenceDiagram participant Browser participant QUIC as QUIC Connection participant Server Note over Browser,Server: HTTP/3 multiplexes 3 streams on 1 QUIC connection Browser->>QUIC: Stream A: Request /image.jpg Browser->>QUIC: Stream B: Request /style.css Browser->>QUIC: Stream C: Request /script.js Server->>QUIC: Stream A packet 1, 2, 3 Server->>QUIC: Stream B packet 1, 2 Server->>QUIC: Stream C packet 1 Note over QUIC: Stream B packet 2 LOST in network QUIC->>Browser: Stream A packet 1 βœ“ QUIC->>Browser: Stream A packet 2 βœ“ QUIC->>Browser: Stream A packet 3 βœ“ (delivered immediately!) Note over QUIC: Stream B blocked waiting for packet 2 QUIC->>Browser: Stream C packet 1 βœ“ (delivered immediately!) Note over QUIC: Only Stream B is blocked
Streams A and C continue independently Server->>QUIC: Stream B packet 2 retransmitted (1 RTT later) QUIC->>Browser: Stream B packet 1 βœ“ QUIC->>Browser: Stream B packet 2 βœ“

Result: Stream A and Stream C complete immediately. Only Stream B waits for its lost packet. No cross-stream blocking.

Real-World Impact

Network Condition HTTP/2 (TCP) HTTP/3 (QUIC) Improvement
1% packet loss Page load: 5.2s
(frequent HOL blocking)
Page load: 4.5s
(only affected streams block)
15% faster
5% packet loss Page load: 12.8s
(severe HOL blocking)
Page load: 6.1s
(streams mostly independent)
52% faster
Mobile (3G, ~2% loss) Video rebuffer rate: 18% Video rebuffer rate: 9% 50% reduction
Low-latency network
(0.1% loss)
Minimal difference Minimal difference ~5% faster (handshake savings)

Source: Google measured these results deploying HTTP/3 to YouTube and Chrome.

Why QUIC Needed to Be Built on UDP

TCP is implemented in the operating system kernel. Changing TCP behavior (e.g., per-stream delivery) requires OS updates, which take years to deploy (Windows, macOS, Linux, mobile, IoT). UDP is a thin wrapper around IP packetsβ€”applications control all behavior. QUIC implements reliability, congestion control, and multiplexing in userspace (browser/app), allowing rapid iteration without OS changes.

Additional QUIC Benefits

  • 0-RTT Reconnection: Cached connection info allows sending data in first packet (no handshake on reconnect)
  • Connection Migration: Switch from WiFi to cellular without breaking connection (identifies connection by ID, not 4-tuple)
  • Improved Loss Recovery: Better ACK system (per-packet sequence numbers vs TCP's byte-stream sequence numbers)
  • Encrypted by Default: TLS 1.3 integrated into QUIC handshake (not optional)

Interview Tip: TCP vs QUIC HOL Blocking

When asked "Why is HTTP/3 faster than HTTP/2 on mobile?", focus on HOL blocking. TCP's in-order delivery means one lost packet blocks all multiplexed streams, even those whose data arrived. QUIC uses independent streamsβ€”only the affected stream blocks. On a 1% loss network, this results in 15% faster page loads. Emphasize this is most impactful on lossy networks (mobile, WiFi) and less significant on stable datacenter links.

HTTP/1.1 vs HTTP/2 vs HTTP/3 HOL Comparison

Protocol Connections Multiplexing HOL Blocking
HTTP/1.1 6 parallel TCP connections No (1 request per connection at a time) Application-level: One slow response blocks subsequent requests on that connection
(Mitigated by 6 connections)
HTTP/2 + TCP 1 TCP connection Yes (multiple streams) Transport-level: Lost TCP packet blocks all streams
(Worse than HTTP/1.1 on lossy networks!)
HTTP/3 + QUIC 1 QUIC connection Yes (multiple streams) No HOL blocking: Lost packet only blocks its stream

The HTTP/2 Paradox

HTTP/2 can be slower than HTTP/1.1 on lossy networks. HTTP/1.1 uses 6 parallel TCP connections. If one connection loses a packet, only 1/6 of traffic is blocked. HTTP/2 uses 1 TCP connection. If it loses a packet, 100% of traffic is blocked. This is why HTTP/3 was necessaryβ€”HTTP/2's multiplexing is powerful but crippled by TCP's HOL blocking.