- Every Instagram request is an HTTP message, encrypted by TLS, carried by TCP (or QUIC over UDP), inside IP packets that routers forward hop by hop across the internet.
- Latency is dominated by round trips, and round trips are limited by the speed of light (~1 ms of RTT per 100 km of fibre). A cold HTTPS request from Mumbai to a US data center can cost well over a second before any optimisation.
- Setting up a connection costs round trips: TCP handshake (1 RTT) + TLS 1.2 (2 RTT) or TLS 1.3 (1 RTT). QUIC/HTTP/3 merges them into 1 RTT, or 0 RTT on resumption.
- HTTP/1.1 = one request at a time per connection (head-of-line blocking); HTTP/2 = many multiplexed streams over one TCP connection; HTTP/3 = streams over QUIC, with no TCP-level blocking and connection migration β ideal for phones.
- The big wins: reuse connections, terminate TLS near the user (edge PoPs), use TLS 1.3 / QUIC, and send fewer, larger requests.
- Proxies sit in the middle: forward proxies act for clients, reverse proxies (load balancers, gateways, CDNs) act for servers. Real-time features need long polling, SSE or WebSockets.
1. The problem: why should a system designer care about protocols?
When you tap the Instagram icon in Mumbai, the app must fetch your feed from servers that may be thousands of kilometres away. That one tap turns into a DNS lookup, a TCP connection, a TLS handshake, an HTTP request, and then dozens of image downloads β each a conversation made of packets bouncing across undersea cables, routers, carrier networks and load balancers.
System design decisions that look "high level" are really networking decisions in disguise:
- Why have a CDN and edge PoPs? Because round trips across oceans are slow and handshakes need several of them.
- Why does the load balancer terminate TLS? Because TLS is CPU-heavy and certificates need central management.
- Why do DMs use WebSockets, not plain HTTP? Because HTTP is request/response and the server can't otherwise speak first.
- Why did Meta invest heavily in QUIC? Because phones switch networks and lose packets, and TCP handles both badly.
You don't need to be a network engineer to design systems, but you do need a solid mental model of what a request costs. This lesson gives you that model and the vocabulary interviewers expect.
2. Layers: the simplified OSI and TCP/IP models
Networking is organised in layers. Each layer solves one problem and relies on the layer below, so HTTP doesn't need to know whether it's travelling over Wi-Fi or 5G. The academic OSI model has 7 layers; the practical TCP/IP model used on the internet collapses them into 4β5. You'll hear engineers say "L4" and "L7" constantly β those numbers come from OSI.
| OSI layer | TCP/IP layer | Job | Examples | Unit / addressing |
|---|---|---|---|---|
| L7 Application (L6 Presentation, L5 Session folded in) | Application | What the program means: requests, responses, encryption and encoding | HTTP, gRPC, DNS, WebSocket, TLS* | Messages; URLs, hostnames |
| L4 Transport | Transport | Process-to-process delivery, reliability, ordering, congestion control | TCP, UDP, QUIC (over UDP) | Segments / datagrams; ports |
| L3 Network | Internet | Host-to-host delivery across many networks; routing | IPv4, IPv6, ICMP, BGP (routing protocol) | Packets; IP addresses |
| L2 Data link | Link | Delivery to the next hop on the same local network | Ethernet, Wi-Fi (802.11), LTE/5G radio | Frames; MAC addresses |
| L1 Physical | Bits as light, electricity or radio waves | Fibre, copper, radio | Bits |
*TLS sits between transport and application; people variously call it L5/L6 or "part of L7". In QUIC it is built into the transport itself.
Why it matters for design: an L4 load balancer sees only IPs and ports (fast, protocol-agnostic), while an L7 load balancer parses HTTP and can route by URL path, header or cookie (smarter, more expensive). See Load balancing.
2.1 Encapsulation
As data goes down the stack, each layer wraps it with its own header β like nested envelopes. The receiver unwraps them in reverse order.
3. IP addresses, ports and NAT
3.1 IP addresses
An IP address identifies a network interface on the internet, like a building's street address. IPv4 addresses are 32 bits, written as four numbers (157.240.22.174) β about 4.3 billion total, which ran out long ago. IPv6 addresses are 128 bits (2a03:2880:f12f:83:face:b00c:0:25de β yes, Meta embeds "face:b00c" in some of its IPv6 addresses), enough for every device many times over. Large mobile carriers increasingly run IPv6-first networks, and big services serve both.
Some IPv4 ranges are private (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16): usable inside a home, office or data center but not routable on the public internet. The /8 notation (CIDR) says how many leading bits are the network prefix β /8 means the first 8 bits are fixed, leaving 24 bits (16.7M addresses) for hosts.
Routers forward packets based on the destination address using routing tables. Between organisations (called autonomous systems), routes are exchanged with BGP (Border Gateway Protocol): each network announces "I can reach these address prefixes". If a network stops announcing its prefixes, the rest of the internet literally forgets how to reach it β which is exactly what happened in Facebook's October 2021 outage (see DNS).
3.2 Ports
One machine runs many programs, so an IP address alone isn't enough. A port (a 16-bit number, 0β65535) identifies the program/socket β the apartment number in the building. Well-known server ports: 443 HTTPS (and HTTP/3 over UDP 443), 80 HTTP, 53 DNS, 22 SSH, 5432 PostgreSQL, 6379 Redis, 9092 Kafka. Clients use a random ephemeral port (e.g. 51234) for each outgoing connection.
A TCP connection is uniquely identified by its 5-tuple: (protocol, source IP, source port, destination IP, destination port). Load balancers hash this tuple to keep all packets of one connection on the same backend.
3.3 NAT (Network Address Translation)
Because IPv4 addresses are scarce, your home router gives devices private addresses (192.168.1.23) and translates them to its single public address on the way out, remembering the mapping in a table so replies can be sent back to the right device. Mobile carriers do the same at enormous scale β carrier-grade NAT (CGNAT) β so thousands of phones may share one public IPv4 address.
Phone 10.20.3.7:51234 βββ βββ> 157.240.x.x:443 (Instagram edge)
Phone 10.20.9.1:40001 βββΌβ> Carrier NAT 49.36.1.5 ββ€ sees both as 49.36.1.5 (ports 61001, 61002)
Phone 10.20.4.4:33333 βββ table: βββ> ...
10.20.3.7:51234 β 49.36.1.5:61001
10.20.9.1:40001 β 49.36.1.5:61002 (entries expire when idle!)
Design consequences:
- Idle connections die silently. NAT tables drop idle entries (sometimes after only 30 seconds to a few minutes on mobile). Long-lived connections like DM WebSockets or MQTT must send small keep-alive heartbeats, or the NAT forgets them and the server's next message vanishes.
- IP-based rate limiting is crude. Thousands of legitimate users can share one carrier IP; limiting per IP would punish all of them. Rate limit per user/token where possible (see Rate limiting).
- Servers can't initiate connections to phones. The phone has no public address; the server can only talk back over a connection the phone opened β which is why push uses APNs/FCM or a client-opened persistent connection.
4. TCP vs UDP
The transport layer offers two classic choices:
- TCP (Transmission Control Protocol) gives a reliable, ordered byte stream: what you send arrives complete, in order, without duplicates β or the connection reports an error. It achieves this with handshakes, sequence numbers, acknowledgements and retransmissions, and it slows down when the network is congested.
- UDP (User Datagram Protocol) sends individual datagrams with almost nothing added: no connection, no delivery guarantee, no ordering, no congestion control. It's a thin layer over IP that adds ports and a checksum. Applications that need reliability build their own on top (as QUIC does).
| TCP | UDP | |
|---|---|---|
| Connection | Connection-oriented (handshake first) | Connectionless β just send |
| Reliability | Guaranteed delivery via ACKs and retransmission | Best effort; packets may be lost |
| Ordering | Strictly in order | None β may arrive out of order or duplicated |
| Congestion / flow control | Built in | None (app must be polite itself) |
| Header size | 20β60 bytes | 8 bytes |
| Setup latency | 1 RTT before data | 0 RTT |
| Head-of-line blocking | Yes β one lost packet stalls everything behind it | No (each datagram independent) |
| Typical uses | HTTP/1.1 & HTTP/2, databases, Kafka, SSH, email | DNS queries, VoIP/video calls, gaming, QUIC/HTTP/3 |
Rule of thumb: if losing or reordering data is unacceptable and you don't want to build reliability yourself, use TCP. If timeliness beats completeness (a video call would rather skip a late frame than wait), or you want to implement a smarter transport in user space (QUIC), use UDP.
5. How TCP works
5.1 The three-way handshake
Before any data flows, client and server agree to talk and exchange starting sequence numbers (counters used to number every byte so the receiver can reorder and detect gaps):
Client (phone) Server (edge LB)
| SYN seq=x "I want to talk; my bytes start at x" |
|-----------------------------------------------------------> |
| SYN-ACK seq=y, ack=x+1 "OK; mine start at y; got your x" |
| <----------------------------------------------------------- |
| ACK ack=y+1 (can already carry the first request bytes) |
|-----------------------------------------------------------> |
|<================= 1 full RTT before the request goes ========>|
The client can send data together with the final ACK, so the handshake costs exactly one round trip before the request leaves. Closing a connection uses a FIN/ACK exchange; an abrupt close uses a RST packet. (TCP Fast Open can send data in the SYN on repeat connections, but middlebox problems limited its adoption β one of the motivations for QUIC.)
5.2 Reliability: sequence numbers, ACKs and retransmission
- Every byte has a sequence number. The receiver sends ACKs ("I've received everything up to byte 14,600").
- If the sender doesn't get an ACK within a retransmission timeout (RTO) β derived from measured RTT β or sees three duplicate ACKs (a strong hint a packet was lost), it resends.
- The receiver reassembles bytes in order and hands the application a clean stream. A gap means later bytes wait in a buffer until the missing packet arrives: this is TCP head-of-line blocking.
- Flow control: the receiver advertises a receive window ("I have buffer space for 64 KB more") so a fast sender can't overwhelm a slow receiver.
5.3 Congestion control: the intuition
The network itself has limited capacity. If every sender blasted at full speed, router queues would overflow and everyone's packets would be dropped (congestion collapse). TCP therefore keeps a congestion window (cwnd) β how many unacknowledged bytes it may have "in flight":
- Slow start: a new connection starts small β typically 10 segments (β14 KB) β and doubles cwnd every RTT while ACKs keep coming back: 14 KB β 28 β 56 β 112β¦
- Congestion avoidance: after a threshold, grow more gently (roughly +1 segment per RTT).
- On loss: assume congestion and cut cwnd (classic algorithms halve it β "additive increase, multiplicative decrease", AIMD). Modern Linux defaults to CUBIC; Google's BBR instead estimates bottleneck bandwidth and RTT directly and copes better with random (non-congestion) loss common on wireless links.
The consequence for designers: a new connection is slow even on a fat pipe. Downloading a 100 KB feed response on a fresh connection needs several RTTs of slow start (14 + 28 + 56 = 98 KB after 3 RTTs). On a reused, "warm" connection whose cwnd has already grown, the same response may arrive in one RTT. That's another reason to reuse connections and to place servers close to users.
bytes delivered after k RTTs of slow start β initcwnd Γ (2^k β 1)
initcwnd = 14 KB: k=1 β 14 KB, k=2 β 42 KB, k=3 β 98 KB, k=4 β 210 KB
β a 200 KB image on a brand-new connection needs ~4 RTTs of transfer time.
Mumbai β local PoP (20 ms RTT): ~80 ms. Mumbai β US (220 ms RTT): ~880 ms.
6. TLS: encryption and its handshake cost
TLS (Transport Layer Security) β the "S" in HTTPS β gives three guarantees: confidentiality (nobody on the path can read the data), integrity (nobody can modify it undetected) and authentication (you're really talking to instagram.com, not an impostor on the cafΓ© Wi-Fi).
6.1 The building blocks
- Certificates: the server presents a certificate binding its hostname to a public key, signed by a Certificate Authority (CA) the device already trusts. The client verifies the chain (leaf β intermediate β root) and the hostname.
- Key exchange: client and server use (elliptic-curve) DiffieβHellman to agree on a shared secret over the open network without ever sending it. With ephemeral keys this gives forward secrecy: stealing the server's private key later doesn't decrypt old recorded traffic.
- Symmetric encryption: the actual data is encrypted with fast ciphers (AES-GCM, ChaCha20-Poly1305) using keys derived from that secret. Asymmetric crypto is only used in the handshake because it's expensive.
- SNI (Server Name Indication): the client says which hostname it wants in the first message, so one IP/load balancer can serve many certificates (
i.instagram.com,www.instagram.com, CDN hosts).
6.2 TLS 1.2 vs TLS 1.3 handshakes
TLS 1.2 (after TCP handshake) TLS 1.3 (after TCP handshake)
Client Server Client Server
| ClientHello -------------> | | ClientHello + key share -> |
| <- ServerHello, Cert, | | <- ServerHello + key share,|
| ServerKeyExchange, Done | RTT 1 | Cert, Finished | RTT 1
| ClientKeyExchange, | | Finished + HTTP request -> |
| ChangeCipherSpec, Finished->| | <------- HTTP response ---- | RTT 2
| <- ChangeCipherSpec,Finished| RTT 2 |
| HTTP request -------------->| | Resumed with PSK + 0-RTT:
| <------------ HTTP response | RTT 3 | ClientHello + early data (request) ->
| <- response | RTT 1 (total)
| Setup | Handshake RTTs before request | Cold request total (incl. request RTT) | Notes |
|---|---|---|---|
| TCP + TLS 1.2 | 1 (TCP) + 2 (TLS) = 3 | 4 RTT | Legacy; still common on old devices |
| TCP + TLS 1.2 with session resumption | 1 + 1 = 2 | 3 RTT | Session IDs / tickets skip the certificate step |
| TCP + TLS 1.3 | 1 + 1 = 2 | 3 RTT | Key share sent in the first message |
| TCP + TLS 1.3 0-RTT | 1 + 0 = 1 | 2 RTT | Request rides with ClientHello on resumption |
| QUIC (HTTP/3), first visit | 1 (transport + crypto combined) | 2 RTT | TLS 1.3 built into QUIC |
| QUIC 0-RTT resumption | 0 | 1 RTT | Request in the very first packet |
GET feed), never for "post this comment" or "transfer money". This is another place where idempotency (see API design) matters.Where TLS ends is a design decision. Instagram-style systems terminate TLS at the edge β the CDN PoP or the edge load balancer β close to the user, so the expensive handshake round trips are short. Traffic from the edge to the data center travels over long-lived, pre-warmed, re-encrypted connections. Inside the data center, service-to-service calls are increasingly encrypted too (mutual TLS) β see Security.
7. HTTP: the language of the web and of APIs
HTTP (HyperText Transfer Protocol) is a request/response protocol: the client sends a request, the server sends exactly one response. Every Instagram API call β load feed, like, follow, get an upload URL β is an HTTP request (REST/GraphQL-style, see API design), and every image download from the CDN is an HTTP GET.
7.1 Anatomy of a request and response
POST /api/v1/media/3141592653/like/ HTTP/1.1 β request line: METHOD PATH VERSION
Host: i.instagram.com β headers: metadata about the request
User-Agent: Instagram 300.0.0.0 Android (33/13; β¦)
Authorization: Bearer eyJhbGciOiβ¦ β who you are (see Authentication lesson)
Content-Type: application/json
Content-Length: 46
Idempotency-Key: 7f3e2a10-β¦ β makes retries safe
Accept-Encoding: gzip, br
β blank line ends the headers
{"container_module":"feed_timeline","radio":"wifi"} β body (optional)
HTTP/1.1 200 OK β status line: VERSION CODE REASON
Content-Type: application/json
Content-Encoding: gzip
Cache-Control: private, no-store β personalised: never cache in shared caches
X-Request-Id: 9b1cβ¦ β for tracing (see Observability)
{"status":"ok","like_count":57,"has_liked":true}
Parts to know: the method (what to do), the path (which resource), headers (metadata β auth, content type, caching, compression), and the optional body. Responses carry a status code, headers and body. In HTTP/2 and HTTP/3 the same concepts exist, but the request line becomes "pseudo-headers" (:method, :path, :authority) in a compressed binary encoding.
7.2 Methods
| Method | Meaning | Safe? (no side effects) | Idempotent? (repeat = same effect) | Instagram example |
|---|---|---|---|---|
GET | Read a resource | β | β | Load feed page, fetch a profile, download an image |
HEAD | Like GET but headers only | β | β | Check whether a media object exists / its size |
POST | Create / perform an action | β | β (unless you add an idempotency key) | Create a post, add a comment |
PUT | Create or replace at a known URL | β | β | Upload a chunk to a pre-signed URL; set "I like this" |
PATCH | Partially update | β | Not necessarily | Edit bio or caption |
DELETE | Remove | β | β | Delete a comment, unfollow |
OPTIONS | Ask what's allowed | β | β | Browser CORS pre-flight for the web app |
Idempotency matters because networks fail: if a response is lost, the client doesn't know whether the server acted. Retrying an idempotent request is always safe; retrying a non-idempotent one needs an idempotency key the server deduplicates. See Resilience patterns.
7.3 Status codes
| Code | Meaning | When you'll see it in Instagram's design | Retry? |
|---|---|---|---|
| 200 OK | Success with body | Feed page returned | β |
| 201 Created | Resource created | New post or comment created | β |
| 204 No Content | Success, no body | Unfollow done | β |
| 206 Partial Content | Range of bytes returned | Video player fetching byte ranges from the CDN | β |
| 301 / 302 / 307 / 308 | Redirect (permanent / temporary) | http:// β https://; short links | Follow |
| 304 Not Modified | Your cached copy is still valid | ETag revalidation by the app or CDN | β |
| 400 Bad Request | Malformed request | Missing field, invalid JSON | β fix the request |
| 401 Unauthorized | Not authenticated | Expired access token β refresh it, then retry | After re-auth |
| 403 Forbidden | Authenticated but not allowed | Viewing a private account you don't follow; blocked user | β |
| 404 Not Found | No such resource | Deleted post | β |
| 409 Conflict | State conflict | Username already taken | β |
| 413 Payload Too Large | Body too big | Video exceeds upload size limit | β |
| 429 Too Many Requests | Rate limited | Too many follows/likes per hour (see Rate limiting) | β
after Retry-After |
| 500 Internal Server Error | Bug / unhandled failure | Service exception | β with backoff (if idempotent) |
| 502 Bad Gateway | Proxy got a bad response from upstream | LB/gateway couldn't reach a healthy service | β with backoff |
| 503 Service Unavailable | Overloaded or down | Load shedding during a spike | β with backoff |
| 504 Gateway Timeout | Upstream didn't answer in time | Feed service exceeded the gateway's timeout | β with backoff (if idempotent) |
Memory aid: 2xx = success, 3xx = go elsewhere / use your cache, 4xx = you (the client) did something wrong, 5xx = we (the server) did.
8. HTTP/1.1: keep-alive and head-of-line blocking
HTTP/1.0 opened a new TCP connection for every request β a handshake (and later a TLS handshake) per image. HTTP/1.1 (1997) made connections persistent by default (keep-alive): after a response, the connection stays open for the next request, saving the handshake round trips and keeping TCP's congestion window warm.
But HTTP/1.1 is still one request at a time per connection: the response to request #1 must be fully received before #2's response can start. (HTTP pipelining tried to let clients send several requests without waiting, but responses still had to come back in order, and buggy proxies made it unusable β browsers disabled it.) If response #1 is slow β a big image or a slow database query β everything queued behind it waits. This is application-level head-of-line (HOL) blocking.
Workarounds became folklore:
- Multiple connections: browsers open up to ~6 connections per hostname in parallel β six times the handshakes and six congestion windows competing.
- Domain sharding: spread assets over
img1.example.com,img2β¦to get 6 connections each. More DNS lookups, more handshakes. - Spriting and bundling: glue many small images or scripts into one file to reduce request count.
All of these are hacks around a protocol limitation β and they actively hurt under HTTP/2.
9. HTTP/2: multiplexing over one connection
HTTP/2 (2015) keeps HTTP's semantics (methods, headers, status codes) but changes how they travel:
- Binary framing: messages are split into small frames, each labelled with a stream ID.
- Multiplexing: many requests and responses (streams) are interleaved on one TCP connection. The 30 thumbnails of a profile grid can all be in flight at once; a slow response no longer blocks the others at the HTTP layer.
- Header compression (HPACK): headers such as cookies, user-agent and auth tokens repeat on every request (often 500β1,000+ bytes). HPACK keeps a table of previously sent headers on both sides and sends small indexes instead β commonly shrinking headers by 80β90%. On a slow uplink, that's a real saving.
- Prioritisation: clients can hint which streams matter more (the on-screen image before a prefetch).
- Server push: the server could send resources before they were requested. It proved hard to use well and major browsers have since removed support β mention it only as history.
The remaining problem: HTTP/2 still runs over TCP, which delivers one ordered byte stream. If a single packet is lost, TCP holds back all later bytes β belonging to all streams β until the retransmission arrives. On a lossy mobile network, HTTP/2's one-connection design can actually suffer more than HTTP/1.1's six connections. This is transport-level head-of-line blocking, and it's what HTTP/3 fixes.
10. HTTP/3 and QUIC: built for mobile
QUIC is a transport protocol originally developed at Google and standardised by the IETF in 2021 (RFC 9000); HTTP/3 is HTTP mapped onto QUIC. QUIC runs over UDP β not because it wants to be unreliable, but because UDP passes through existing routers, NATs and firewalls, letting QUIC implement its own reliable transport in user space instead of inside the OS kernel. That means it can evolve with an app update rather than waiting years for operating systems to change.
10.1 What QUIC changes
- Combined handshake: transport setup and TLS 1.3 happen together β 1 RTT for a new connection instead of 2β3 for TCP + TLS.
- 0-RTT resumption: returning clients can send the request in the first packet (with the replay caveats above).
- Independent streams: reliability and ordering are per stream, so a lost packet only stalls the stream it belongs to β no transport-level head-of-line blocking.
- Connection migration: TCP connections are identified by the 5-tuple, so when your phone moves from Wi-Fi to 4G (new IP address), every TCP connection breaks and must re-handshake. QUIC identifies connections by a connection ID instead, so the same connection continues across the network switch β a video keeps playing as you walk out of your house.
- Always encrypted, including most transport headers, which also stops middleboxes from ossifying the protocol.
- Better loss recovery: unambiguous packet numbers and richer ACKs make retransmission decisions more accurate; congestion control is pluggable (e.g. BBR).
Costs: QUIC uses more CPU per byte than kernel TCP (encryption of every packet, user-space processing), some corporate networks block UDP 443 (so clients must fall back to TCP β typically racing both, "happy eyeballs" style, or remembering what worked), and L4 load balancers must route by connection ID rather than the 5-tuple to support migration.
| HTTP/1.1 | HTTP/2 | HTTP/3 | |
|---|---|---|---|
| Transport | TCP | TCP | QUIC over UDP |
| Format | Text | Binary frames | Binary frames |
| Concurrency per connection | 1 request at a time | Many multiplexed streams | Many independent streams |
| Head-of-line blocking | HTTP-level and TCP-level | TCP-level only | None across streams |
| Header compression | None | HPACK | QPACK |
| New connection setup (with TLS) | 2β3 RTT | 2β3 RTT | 1 RTT (0 on resumption) |
| Survives network change | No | No | Yes (connection migration) |
| Typical connections per host | ~6 | 1 | 1 |
11. Latency math: what a request really costs
11.1 The physics
Light in optical fibre travels at about 200,000 km/s (two-thirds of its speed in vacuum). So:
one-way delay β 5 Β΅s per km β 1 ms per 200 km
round trip (RTT) β 1 ms per 100 km of distance (theoretical floor)
Mumbai β US East (Virginia): ~13,000 km great-circle
floor RTT = 2 Γ 13,000 km / 200,000 km/s = 130 ms
real cables detour (via Europe or Asia-Pacific), plus router hops and queues
β assume a measured RTT of ~220 ms
No amount of engineering beats the speed of light. You can only (1) reduce the number of round trips or (2) reduce the distance each round trip travels, by moving servers (or at least the connection endpoint) closer to the user.
11.2 Worked example: the first request from Mumbai to a US data center
Assume a user in Mumbai on a good fibre/Wi-Fi connection opens the app and fetches a 60 KB (compressed) feed response. Assume: the DNS answer is cached at a nearby ISP resolver (20 ms), RTT to the US data center is 220 ms, RTT to an Instagram edge PoP in Mumbai is 15 ms, the server takes 50 ms to build the response, and TCP's initial window is ~14 KB (so 60 KB needs the first window plus 2 more RTTs of slow start: 14 + 28 + 56 β₯ 60).
| Step | A: TCP + TLS 1.2, direct to US | B: TCP + TLS 1.3, direct | C: QUIC (1-RTT), direct | D: QUIC 0-RTT resumption, direct | E: TLS 1.3 terminated at Mumbai edge, warm edgeβUS link |
|---|---|---|---|---|---|
| DNS lookup | 20 | 20 | 20 | 20 | 20 |
| TCP handshake | 220 | 220 | 220 (combined) | 0 | 15 |
| TLS handshake | 440 (2 RTT) | 220 (1 RTT) | 15 | ||
| Request β response first bytes | 220 + 50 | 220 + 50 | 220 + 50 | 220 + 50 | 15 + 220 + 50 |
| Rest of response (slow start: 2 more RTT) | 440 | 440 | 440 | 440 | 30 (2 Γ 15; edgeβUS link already warm) |
| Total (ms) | β 1,390 | β 1,170 | β 950 | β 730 | β 365 |
Lessons from the numbers:
- In case A, the server's own 50 ms is under 4% of the total. Round trips, not server time, dominate a cold request over long distances.
- Protocol upgrades (A β B β C β D) cut 200β650 ms by removing round trips.
- Terminating connections near the user (E) is the biggest single win: the handshakes and slow-start ramp happen over a 15 ms path, and only one round trip crosses the ocean β over a long-lived, pre-warmed connection the edge keeps open to the data center. This is why Meta, Google and CDNs run edge PoPs (Meta's edge runs Proxygen) even for uncacheable API traffic. See CDN.
- On a reused warm connection, a follow-up request directly to the US costs only ~220 + 50 = 270 ms β so connection reuse matters as much as anything (see Clients).
- On 4G, add the radio's own latency (assume +30β80 ms per round trip), which multiplies across every RTT above β another reason to minimise round trips.
- To get below ~100 ms you need the data close to the user too: cached media at the CDN, or a regional data center serving India (see Multi-region).
11.3 Latency vs bandwidth
Bandwidth is how wide the pipe is; latency is how long it is. For the small requests that dominate API traffic (a few KB), latency dominates: doubling bandwidth from 50 to 100 Mbps changes almost nothing for a 5 KB request, while halving the RTT nearly halves its time. For big transfers (a 50 MB video upload), bandwidth dominates. The bandwidth-delay product (bandwidth Γ RTT) tells you how many bytes must be in flight to fill the pipe: 100 Mbps Γ 0.22 s β 2.75 MB β far more than a fresh connection's 14 KB window, which is why long-distance transfers take many RTTs to reach full speed.
12. Forward proxies vs reverse proxies
A proxy is an intermediary that receives a request and makes it again on someone's behalf. The key question is: on whose behalf?
| Forward proxy | Reverse proxy | |
|---|---|---|
| Acts on behalf of | Clients | Servers |
| Who configures it | The client side (browser/OS settings, corporate network) | The service owner (DNS points at it) |
| Client knows it's there? | Usually yes | No β it looks like the real server |
| Typical uses | Corporate filtering and logging, caching for an office, anonymity/VPN-like egress, bypassing geo-restrictions | Load balancing, TLS termination, caching, compression, auth, rate limiting, DDoS protection, routing to microservices |
| Examples | Squid, corporate secure web gateways | NGINX, HAProxy, Envoy, Proxygen, CDNs, AWS ALB, API gateways |
In Instagram's diagram, every box between the client and the services is a reverse proxy: the CDN edge, the load balancer and the API gateway. Each terminates a connection from the previous hop and opens (or reuses) one to the next. A practical consequence: the backend sees the proxy's IP, not the user's, so proxies add headers like X-Forwarded-For (or the standard Forwarded) carrying the original client IP β needed for rate limiting, abuse detection and geo features. Only trust those headers when set by your own proxies.
Forward proxies matter to Instagram mainly as a fact of life: users behind corporate proxies and middleboxes, which may block UDP (forcing a QUIC β TCP fallback) or interfere with long-lived connections.
13. Real-time on the web: long polling, SSE and WebSockets
HTTP's request/response model has a limitation: the server can only answer, never speak first. Features like DMs, typing indicators, live comments and "new posts" banners need the server to push data when something happens. Three techniques build that on top of HTTP-era infrastructure:
- Long polling: the client sends a request; the server holds it open until there is news (or ~30β60 s pass), then responds; the client immediately sends the next request. Works everywhere, but each message costs a full request/response, and there's a gap while reconnecting.
- Server-Sent Events (SSE): one long-lived HTTP response that the server keeps streaming events into (
Content-Type: text/event-stream). Simple, works through most proxies, auto-reconnects in browsers β but one-directional (server β client). Popular for live feeds, notifications and streaming AI responses. - WebSockets: start as an HTTP request with an
Upgrade: websocketheader; the server replies101 Switching Protocols, and the same TCP connection becomes a full-duplex message channel. Either side can send small framed messages at any time with a few bytes of overhead.
GET /chat HTTP/1.1
Host: edge-chat.instagram.example
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
β¦ from here on: binary/text frames in both directions β¦
| Short polling | Long polling | SSE | WebSocket | |
|---|---|---|---|---|
| Direction | Client pulls | Client pulls, server delays answer | Server β client | Both ways |
| Latency | Up to the poll interval | Near real-time | Real-time | Real-time |
| Overhead per message | Full HTTP request, mostly empty | Full HTTP request | Small | Very small (2β14 byte frame header) |
| Server cost | Many wasted requests | One held request per client | One open connection per client | One open connection per client |
| Good for | Rare updates, prototypes | Fallback when sockets are blocked | Live counters, feeds, notifications on web | Chat, typing indicators, presence, games |
Persistent connections shift the scaling problem: instead of many short requests, servers must hold millions of mostly idle connections, send heartbeats to keep NAT mappings alive, and route a message to whichever server holds the recipient's connection. That's the core of Direct messaging; protocol selection is compared in API design, and load-balancing long-lived connections is covered in Load balancing. On mobile, Meta has publicly described using MQTT β a lightweight publish/subscribe protocol designed for unreliable networks β over persistent connections for its messaging features.
14. How Instagram uses all of this
Put the pieces together for one feed load and one image, following the arrows of the main diagram:
- DNS resolves the API hostname to a nearby edge PoP's (often anycast) address β see DNS.
- The app opens one QUIC connection (falling back to TCP + TLS 1.3 if UDP is blocked) and keeps it alive for the whole session; all API calls are multiplexed over it.
- The edge PoP terminates QUIC/TLS a few milliseconds from the user, then forwards the request over long-lived connections on Meta's private backbone to a data center region β so only one long round trip is paid per request.
- Inside the data center, the load balancer tiers and the API gateway route to the Feed service; internal calls use HTTP/2-based RPC with pooled connections.
- The JSON response contains CDN URLs; the app fetches images and video segments over a separate, also-reused connection to the CDN hostname β usually served straight from the PoP's cache. See CDN.
- For DMs and live updates, the app keeps a separate persistent connection (MQTT-style) with periodic heartbeats; in the background, notifications arrive through APNs/FCM.
- Meta's edge runs Proxygen (its open-source C++ HTTP framework, used as the L7 reverse proxy) behind Katran (its open-source XDP/eBPF-based L4 load balancer), and serves HTTP/3 via mvfst.
- Meta has publicly reported that the majority of its mobile app traffic, including Instagram, runs over QUIC, with the largest improvements for users on poor networks.
- TLS is terminated at the edge with modern TLS 1.3; the apps also use session resumption so returning users skip full handshakes. (Meta previously built its own 0-RTT protocol, "Zero", before TLS 1.3 standardised the idea.)
- API traffic and media traffic use different hostnames (an API host vs
scontent-*.cdninstagram.com-style CDN hosts), so each path can be routed, cached and scaled independently.
Rough numbers
Assume 500M DAU Γ 50 API requests/day = 25 billion requests/day β 290K QPS average (~600K peak)
If each request opened a NEW connection with TCP + TLS 1.2 (3 extra RTTs at ~100 ms average RTT):
wasted user time = 25B Γ 300 ms β 7.5 billion seconds/day β 240 user-years per day
plus ~290K TLS handshakes/s of server CPU
With one long-lived QUIC connection per session (~10 sessions/user/day):
handshakes drop to 5B/day β 58K/s, most of them 0-RTT resumptions.
15. Trade-offs & common pitfalls
β Modern choices (HTTP/2β3, TLS 1.3, edge termination)
- Far fewer round trips per request
- One connection per host; less handshake CPU
- Better behaviour on lossy, changing mobile networks (QUIC)
- Header compression saves uplink bytes
- Encryption everywhere without a big latency penalty
β οΈ Costs and caveats
- QUIC costs more CPU per byte and some networks block UDP β need TCP fallback
- 0-RTT data is replayable β only for idempotent requests
- One multiplexed TCP connection suffers badly from packet loss (HTTP/2)
- Long-lived connections complicate load balancing and deploys
- Edge PoPs and backbones are expensive to build and operate
Common pitfalls
- Ignoring round trips: optimising server time from 50 to 30 ms while the client pays 1,000 ms of handshakes.
- Creating a new HTTP client per request in app or service code, defeating connection pooling.
- Keeping HTTP/1.1-era hacks (domain sharding, sprites) after moving to HTTP/2 β they add connections and DNS lookups and break multiplexing benefits.
- Retrying non-idempotent requests blindly after a timeout (duplicate comments, double posts).
- No heartbeats on long-lived connections: NATs and load balancers silently drop idle connections; messages vanish.
- Trusting
X-Forwarded-Forfrom the internet: anyone can set it; only accept it from your own proxies. - Treating 4xx as retryable (hammering the server with the same invalid request) or 5xx/429 as permanent.
- Huge cookies/headers sent on every request β even compressed, they cost bytes on the first request of every connection.
16. Real-world technologies
| Technology | What it is | When you'd use / meet it |
|---|---|---|
| NGINX, HAProxy, Envoy | Reverse proxies / L7 load balancers supporting HTTP/1.1, HTTP/2 (and HTTP/3 in newer versions) | Edge and service proxies, TLS termination |
| Proxygen (Meta), mvfst (Meta) | Meta's open-source HTTP library/proxy and QUIC implementation | Meta's edge and apps; reference for large-scale HTTP/3 |
| quiche (Cloudflare), MsQuic (Microsoft), Cronet (Chromium) | Other QUIC/HTTP/3 implementations; Cronet brings Chrome's network stack to Android/iOS apps | Adding HTTP/3 to servers or mobile apps |
| OpenSSL, BoringSSL, rustls | TLS libraries | Anything that terminates or originates TLS |
| Let's Encrypt / ACME, cloud certificate managers | Automated certificate issuance and renewal | Avoiding expired-certificate outages |
| gRPC (over HTTP/2) | RPC framework with Protobuf, streaming, deadlines | Service-to-service calls inside the data center |
| Socket.IO, MQTT brokers, WebSocket gateways | Real-time persistent connection frameworks | Chat, presence, live updates |
| curl, Wireshark, tcpdump, Chrome DevTools, mtr | Debugging tools: inspect requests, packets, timing and routes | Diagnosing latency, handshake and protocol issues |
17. Interview corner
Walk me through what happens when the Instagram app requests the feed.
The app resolves the API hostname via DNS (likely cached; otherwise the recursive resolver walks root β TLD β authoritative and returns a nearby edge IP). It opens a connection β ideally reusing an existing one; otherwise a QUIC 1-RTT handshake (or TCP SYN/SYN-ACK/ACK plus a TLS 1.3 handshake) with the nearest edge PoP, which presents a certificate for the hostname. It sends an HTTP GET with an auth token. The edge terminates TLS and forwards the request over a warm backbone connection to a data center, through L4 and L7 load balancers to the API gateway, which authenticates, rate-limits and routes to the Feed service. The JSON response flows back with CDN URLs for media, which the app then fetches from the CDN over another reused connection.
TCP or UDP for a new real-time video-call feature? For DMs?
Video calls: UDP-based (RTP/WebRTC or QUIC datagrams) β a late frame is useless, so you'd rather drop it than stall everything waiting for a retransmission; the app handles loss with codecs and FEC. DMs: every message must arrive, in order, exactly once from the user's perspective, so a reliable transport β TCP (WebSocket/MQTT) or QUIC streams β plus app-level acknowledgements and IDs for deduplication.
What is head-of-line blocking, and how do HTTP/2 and HTTP/3 address it?
HOL blocking is when one slow or lost item delays everything queued behind it. HTTP/1.1 has it at the application level (one response at a time per connection). HTTP/2 multiplexes streams, fixing the HTTP-level problem, but TCP still delivers one ordered byte stream, so a single lost packet stalls all streams. HTTP/3 runs streams over QUIC, which handles ordering per stream; a lost packet only delays the stream it belonged to.
Why is QUIC especially good for mobile apps like Instagram?
Four reasons: (1) fewer round trips β 1-RTT setup and 0-RTT resumption, which matters on high-RTT mobile links; (2) no transport HOL blocking, which matters on lossy radio links; (3) connection migration via connection IDs, so switching Wi-Fi β cellular doesn't kill in-flight requests and video; (4) it lives in user space, so the app and server can ship improved congestion control and loss recovery without waiting for OS updates. Costs: more CPU and the need for a TCP fallback when UDP is blocked.
A user in India sees 1.5 s feed loads while server-side latency is 60 ms. Explain and fix.
The gap is network round trips: DNS, TCP, TLS (2 RTT on TLS 1.2), the request, and slow start for the response β each ~220 ms to a US data center. Fixes: terminate connections at an Indian edge PoP so handshakes happen over ~15 ms; keep warm edgeβDC connections; upgrade to TLS 1.3 and QUIC with 0-RTT resumption; reuse one connection per session and pre-connect at launch; reduce response size and the number of sequential calls; eventually serve data from a closer region. The worked example in this lesson shows a drop from ~1,390 ms to ~365 ms just from edge termination and modern protocols.
What's the difference between a forward and a reverse proxy? Which ones appear in Instagram's architecture?
A forward proxy acts for clients (they configure it; destinations see the proxy) β e.g. corporate web filters. A reverse proxy acts for servers (clients think it's the server) and provides load balancing, TLS termination, caching, routing, auth and protection. Instagram's CDN edges, load balancers and API gateway are all reverse proxies. They pass the original client IP via X-Forwarded-For/Forwarded headers.
Which HTTP status codes should a client retry, and how?
Retry transient failures: network errors, timeouts, 429 (respecting Retry-After), 502, 503, 504 and usually 500 β with exponential backoff and jitter, a retry budget, and only for idempotent requests or ones carrying an idempotency key. Don't retry 400, 403, 404, 409, 413 β the same request will fail the same way. 401 means refresh the token, then retry once.
18. Check your understanding
Roughly what is the minimum possible RTT between two cities 6,000 km apart, connected by fibre?
RTT β 2 Γ 6,000 km Γ· 200,000 km/s = 60 ms. Real paths are longer and add router delays, so expect noticeably more (perhaps 80β100 ms).
With a 100 ms RTT, how long before the first response byte arrives on a brand-new TCP + TLS 1.2 connection, versus TLS 1.3, versus QUIC 0-RTT (ignore DNS and server time)?
TLS 1.2: TCP 1 + TLS 2 + request 1 = 4 RTT = 400 ms. TLS 1.3: 1 + 1 + 1 = 300 ms. QUIC 0-RTT: the request goes in the first flight = 1 RTT = 100 ms.
Your phone walks from home Wi-Fi onto 4G in the middle of a Reel download. What happens with HTTP/2 over TCP, and with HTTP/3?
The phone's IP address changes. TCP connections are bound to the 5-tuple, so they break: the app must detect the failure (often after a timeout), reconnect with a full handshake, and re-request β causing a stall. QUIC identifies the connection by a connection ID, so it migrates to the new path after a quick path validation and the download continues.
Why might HTTP/2 perform worse than HTTP/1.1 on a network with 3% packet loss?
HTTP/2 puts all requests on one TCP connection; each lost packet stalls every stream until it's retransmitted, and the single connection's congestion window is cut on each loss. HTTP/1.1's six independent connections each stall only their own request, and losses are spread across six congestion windows.
A fresh TCP connection has an initial window of ~14 KB that doubles every RTT. About how many RTTs does it take to deliver a 400 KB image?
Cumulative: 14, 42, 98, 210, 434 KB β about 5 RTTs. On a 20 ms path to a nearby PoP that's ~100 ms; on a 220 ms path to another continent it's ~1.1 s. This is why media is served from nearby CDN edges and connections are reused (their windows are already large).
Thousands of users of a mobile carrier all appear to your servers from the same IP. Why, and what does that mean for rate limiting?
Carrier-grade NAT maps many private device addresses to one public IPv4 address (distinguished by port). A strict per-IP rate limit would block thousands of innocent users when one misbehaves. Rate limit by authenticated user/token or device instead, and use per-IP limits only as a loose, high threshold for unauthenticated endpoints.
Your DM WebSocket connections keep "silently" dying after a few idle minutes on mobile. What's the likely cause and fix?
NAT devices and intermediate load balancers drop idle mappings/connections after a timeout, without telling either end. Send application-level heartbeats (ping/pong) more often than the shortest idle timeout (e.g. every 30β60 s, tuned to balance battery), detect missing pongs to reconnect quickly, and resynchronise missed messages on reconnect.
Is it safe to accept "POST /comments" as TLS 1.3 0-RTT early data? Why or why not?
No. 0-RTT data can be captured and replayed by an attacker, causing the comment to be posted twice (or more). Only accept safe, idempotent requests (like GET feed) as early data, or require an idempotency key that makes replays harmless.
- Requests are layered: HTTP inside TLS inside TCP/QUIC inside IP; L4 devices see ports and IPs, L7 devices see HTTP.
- Round trips Γ distance dominate latency; ~1 ms RTT per 100 km is a hard floor.
- Connection setup is expensive: TCP (1 RTT) + TLS 1.2 (2) or 1.3 (1); QUIC does 1, or 0 on resumption. Reuse connections and keep them warm.
- HTTP/1.1 β HTTP/2 β HTTP/3 progressively remove head-of-line blocking; QUIC's connection migration is ideal for phones.
- Terminate TLS near the user and carry traffic over warm backbone connections β the biggest single latency win for global apps.
- Reverse proxies (CDN, LB, gateway) front every server; real-time features need long polling, SSE or WebSockets with heartbeats.
Related lessons
- DNS β the lookup that precedes every new connection, and the 2021 BGP/DNS outage
- CDN β edge PoPs that shorten round trips for media and terminate TLS
- Load balancing β L4 vs L7, TLS termination and long-lived connections
- API gateway β the reverse proxy that authenticates and routes API calls
- Clients β connection reuse, batching and retries on the device
- API design β REST vs gRPC vs GraphQL vs WebSockets, idempotency keys
- Direct messaging β WebSockets/MQTT at scale
- Security β TLS everywhere, mTLS and DDoS protection
- Resilience patterns β timeouts, retries and backoff