Contents
Start
Course · 02
What really happens between a click and a reply?
This page follows one request from the address bar to the server and back, then shows how to design the API that answers it: REST, gRPC, GraphQL, and the ways to push live updates.
Every request pays for round trips (DNS, TCP, TLS) before any data moves, and your API design decides how safe it is to retry.
- A fresh HTTPS request over TCP + TLS 1.3 needs 3 round trips before the first byte. HTTP/3 (QUIC) needs 2.
- HTTP/2 fixed head-of-line blocking at the HTTP layer, but not in TCP. HTTP/3 moves to UDP-based QUIC to fix that too.
- GET, PUT and DELETE are idempotent by RFC 9110; POST is not. Make POST safe to retry with an idempotency key.
- For live updates: SSE for server-to-client only, WebSockets for two-way, long polling as the fallback.
—Words you need
The journey
01What happens when you type a URL
You type https://a.com and press Enter. Four things happen in order. First the browser finds the IP address (DNS). Then it opens a connection (TCP), makes it secret (TLS), and finally asks for the page (HTTP).
Part 1: finding the address (DNS)
The browser checks its own cache and the OS cache first. If the name is not there, it asks a recursive resolver (usually your ISP's, or a public one). The resolver walks down the tree: root → top-level domain (.com) → the domain's own authoritative server, which holds the real answer.
Part 2: connecting, securing, asking
Round trips before the first response byte (new connection, DNS not counted)
Fewer is faster. On a 150 ms ocean link, each round trip costs 150 ms. Hover for the breakdown.
Show the full table
| Setup | Transport RTT | Security RTT | Request RTT | Total |
|---|---|---|---|---|
| TCP + TLS 1.2 | 1 | 2 | 1 | 4 |
| TCP + TLS 1.3 | 1 | 1 | 1 | 3 |
| QUIC (HTTP/3) | 1 (combined) | 1 | 2 | |
| QUIC 0-RTT resumption | 0 | 1 | 1 | |
Round trips, not bandwidth, usually decide how fast a page starts. That is why we reuse connections, use CDNs close to users, and prefer TLS 1.3 / HTTP/3.
Sources: RFC 1034 (DNS); RFC 9293 §3.5 (TCP handshake); RFC 8446 §2 (TLS 1.3); RFC 5246 (TLS 1.2); RFC 9001 (QUIC-TLS); root-servers.org
02DNS records and TTL
| Record | Maps | Example | Defined in |
|---|---|---|---|
A | Name → IPv4 address | a.com A 93.184.216.34 | RFC 1035 |
AAAA | Name → IPv6 address | a.com AAAA 2606:2800::1 | RFC 3596 |
CNAME | Name → another name (alias) | www.a.com CNAME a.cdn.net | RFC 1034 |
NS | Zone → its authoritative servers | a.com NS ns1.host.net | RFC 1035 |
MX | Domain → mail server | a.com MX 10 mail.a.com | RFC 1035 |
TXT | Name → free text (domain proofs, SPF) | a.com TXT "v=spf1 ..." | RFC 1035 |
TTL (time to live) is a number of seconds on every record. Resolvers may cache the answer for that long. Short TTL means changes spread fast but more lookups. Long TTL means fewer lookups but slow changes.
a.com itself) must hold SOA and NS records, so a plain CNAME cannot sit at the apex. That is why DNS providers offer non-standard "ALIAS" or "flattening" records for apex-to-CDN setups. The TTL field is a 32-bit number of seconds (RFC 1035 §3.2.1). Note that some resolvers and browsers enforce their own minimum or maximum cache times, so a 30-second TTL does not guarantee a 30-second switch-over.Before a planned migration, lower the TTL a day ahead, so the switch spreads in minutes instead of hours.
Sources: RFC 1034 §3.6.2; RFC 1035 §3.2.1; RFC 3596 (AAAA)
03TCP vs UDP
| Feature | TCP (RFC 9293) | UDP (RFC 768) |
|---|---|---|
| Connection setup | Yes, 3-way handshake | No, just send |
| Delivery guarantee | Yes, resends lost data | No |
| Order guarantee | Yes, one ordered byte stream | No |
| Flow and congestion control | Yes | No (app must add it) |
| Header size | 20 bytes minimum | 8 bytes |
| Used by | HTTP/1.1, HTTP/2, databases, SSH | DNS queries, video calls, games, QUIC (HTTP/3) |
Pick TCP when every byte must arrive in order. Pick UDP when fresh data beats complete data, or when you build your own reliability on top (like QUIC).
Sources: RFC 9293 (TCP); RFC 768 (UDP)
04HTTP/1.1 vs HTTP/2 vs HTTP/3
All three versions share the same semantics (methods, status codes, headers), defined once in RFC 9110. What changes is how bytes travel.
| Feature | HTTP/1.1 (RFC 9112) | HTTP/2 (RFC 9113) | HTTP/3 (RFC 9114) |
|---|---|---|---|
| Format on the wire | Text | Binary frames | Binary frames |
| Requests per connection at once | One at a time (pipelining rarely used) | Many streams multiplexed | Many streams multiplexed |
| Transport | TCP | TCP | QUIC over UDP |
| Head-of-line blocking | At HTTP layer and TCP | Fixed at HTTP layer; still in TCP | Fixed per stream |
| Header compression | None | HPACK (RFC 7541) | QPACK (RFC 9204) |
| Connection survives IP change (Wi-Fi → 4G) | No | No | Yes, connection IDs and migration (RFC 9000 §9) |
HTTP/2 is the default between services; HTTP/3 helps most on lossy mobile networks. Your API design does not change; only the transport does.
Sources: RFC 9110; RFC 9112; RFC 9113 §1; RFC 9114; RFC 9000 §9; RFC 7541; RFC 9204
REST APIs
05REST design
REST is a style for web APIs. You name resources (things) with URLs, like /users/42/orders. You act on them with HTTP methods (verbs). Use plural nouns, not verbs in the URL: POST /orders, not /createOrder.
Methods: safe and idempotent
Safe means read-only: the server state should not change. Idempotent means sending it twice has the same effect as once. Idempotent requests are safe to retry after a timeout.
| Method | Typical use | Safe | Idempotent |
|---|---|---|---|
GET | Read a resource or list | Yes | Yes |
HEAD | Like GET, headers only | Yes | Yes |
OPTIONS | Ask what is allowed (CORS preflight) | Yes | Yes |
PUT | Replace a resource at a known URL | No | Yes |
DELETE | Remove a resource | No | Yes |
POST | Create in a collection, or run an action | No | No |
PATCH | Change part of a resource | No | No (RFC 5789) |
Status codes you should know
| Code | Meaning | When to return it |
|---|---|---|
200 OK | Worked | Successful GET, PUT, PATCH |
201 Created | New resource made | POST that created something; add a Location header |
204 No Content | Worked, nothing to send | DELETE |
304 Not Modified | Your cached copy is still good | Conditional GET with ETag |
400 Bad Request | Malformed request | Bad JSON, missing field |
401 / 403 | Not logged in / not allowed | Missing token / wrong permissions |
404 Not Found | No such resource | Unknown ID |
409 Conflict | Clashes with current state | Version conflict, duplicate unique key |
422 Unprocessable Content | Valid format, invalid meaning | Validation rule failed |
429 Too Many Requests | Rate limited | Add Retry-After (RFC 6585) |
500 / 502 / 503 / 504 | Server-side failure | Bug / bad upstream / overloaded / upstream timeout |
Clients and proxies may retry idempotent requests automatically. Design PUT and DELETE so a repeat is harmless, and protect POST with an idempotency key (section 07).
Sources: RFC 9110 §9.2; RFC 9110 §15 (status codes); RFC 5789 (PATCH); RFC 6585 §4 (429)
06Pagination and versioning
A list endpoint must never return everything at once. It returns one page plus a way to get the next one. There are two common ways.
| Question | Offset: ?limit=20&offset=40 | Cursor: ?limit=20&starting_after=obj_123 |
|---|---|---|
| How it finds the page | Skip N rows, take the next 20 | Start right after a known item (an indexed key) |
| Jump to page 50 | Yes | No, only next / previous |
| Cost of deep pages | Grows with the offset: skipped rows are still read | Same for every page (index seek) |
| New rows inserted while paging | Items shift: duplicates or skips | Stable: no duplicates or skips |
| Good for | Small admin tables with page numbers | Feeds, timelines, big or fast-changing lists |
-- page 1
SELECT * FROM orders WHERE user_id = 42 ORDER BY id DESC LIMIT 20;
-- next page: continue after the last id you saw (here 9871)
SELECT * FROM orders WHERE user_id = 42 AND id < 9871 ORDER BY id DESC LIMIT 20;
Versioning
| Way | Example | Trade-off |
|---|---|---|
| URL path | /v1/orders | Very clear and easy to route; changes every URL on a new major version |
| Header | Stripe-Version: 2026-09-30.endive | Clean URLs, fine-grained dates; harder to test in a browser |
| Query parameter | ?api-version=2024-01-01 | Easy to try; easy to forget |
Whatever the scheme, only add things in a version (new optional fields, new endpoints). Removing or renaming a field is a breaking change and needs a new version.
starting_after and ending_before take an object ID, limit is 1–100 (default 10), and the reply has has_more. For versioning, Stripe pins each account to a dated API version and lets you override it per request with the Stripe-Version header; as of its docs page (checked 2026-10-25) it ships monthly versions without breaking changes and two major releases a year.Default to cursor pagination and return an opaque next_cursor. Offsets are fine only for small tables where users need page numbers.
Sources: PostgreSQL · LIMIT and OFFSET; Stripe · Pagination; Stripe · Versioning (checked 2026-10-25)
07Idempotency keys for POST
Problem: the client sends "charge $10". The server does it, but the reply is lost on the network. The client cannot tell if the charge happened. If it retries a plain POST, the user may pay twice.
Fix: the client makes a unique idempotency key (like a random UUID) and sends it with the request. The server saves the result under that key. A retry with the same key gets the saved result, not a second charge.
POST /v1/charges
Idempotency-Key: 8e1c2f0a-5b7d-4c1e-9a33-2f6b1d0c7e55
Content-Type: application/json
{"amount": 1000, "currency": "usd"}
(key, user, request hash, status, response) with a unique constraint on the key, insert it in the same database transaction as the business change, and expire old keys.Any money-moving or create-something POST should take an idempotency key. It turns "at least once" retries into "exactly once" effects.
Source: Stripe · Idempotent requests (checked 2026-10-25)
Other API styles
08gRPC and Protocol Buffers
gRPC lets one service call a function on another as if it were local. You describe the API in a .proto file. Tools then generate client and server code in many languages.
syntax = "proto3";
service Orders {
rpc GetOrder (GetOrderRequest) returns (Order); // unary
rpc WatchOrders (WatchRequest) returns (stream Order); // server streaming
}
message GetOrderRequest { int64 id = 1; }
message Order {
int64 id = 1; // the number, not the name, goes on the wire
string status = 2;
int64 cents = 3;
}
| gRPC call type | Shape | Example |
|---|---|---|
| Unary | 1 request → 1 response | Get order |
| Server streaming | 1 request → many responses | Live order updates |
| Client streaming | many requests → 1 response | Upload metrics in chunks |
| Bidirectional streaming | both sides stream | Chat, live sync |
DEADLINE_EXCEEDED. The gRPC wire protocol is "gRPC over HTTP2" (the official PROTOCOL-HTTP2.md). Protobuf docs: fields are identified by number; "field numbers in the range 1 through 15 take one byte to encode", and "reusing a field number makes decoding wire-format messages ambiguous", so you never reuse one. Browsers cannot speak raw gRPC, so web clients use gRPC-Web through a proxy such as Envoy.Use gRPC for internal service-to-service calls (typed, compact, streaming). Keep REST/JSON at the public edge where browsers and partners call you.
Sources: grpc.io · Core concepts; grpc.io · Introduction; gRPC over HTTP2; protobuf.dev · proto3 guide; grpc.io · gRPC-Web basics
09GraphQL
GraphQL has one endpoint and a typed schema. The client sends a query listing the exact fields it wants, even across related objects. The server returns that shape and nothing more.
query {
user(id: 42) {
name
orders(first: 3) { id total }
friends(first: 5) { name }
}
}
| Question | REST | GraphQL |
|---|---|---|
| Over-fetching (too many fields) | Common | Avoided: client picks fields |
| Under-fetching (many round trips) | Common for screens with nested data | One query |
| HTTP caching (CDN, browser) | Easy: GET by URL | Harder: usually POST to one URL |
| Server cost control | Fixed per endpoint | Needs depth / complexity limits |
| N+1 database queries | Visible per endpoint | Easy to hit in resolvers; batch them (DataLoader) |
friends runs once per user, causing one DB query per item; Facebook's dataloader library batches those into one.GraphQL shines for many client screens over many back-end services. Pay for it with caching and query-cost controls.
Sources: GraphQL spec (October 2021); github.com/graphql/dataloader
10Real-time updates: polling, long polling, SSE, WebSockets
HTTP is request-response: the server cannot speak first. Four patterns work around that.
- Short polling: ask every N seconds. Simple, but most answers are "nothing new".
- Long polling: ask, and the server holds the request open until there is news or a timeout.
- SSE (server-sent events): one long HTTP response that streams events, server to client only.
- WebSocket: upgrade the connection to a two-way pipe that stays open.
| Option | Direction | Delay | Pick it when | Watch out for |
|---|---|---|---|---|
| Short polling | Client asks | Up to the poll interval | Updates are rare and a few seconds late is fine | Wasted requests at scale |
| Long polling | Client asks, server waits | Near real-time | Fallback where SSE/WebSockets are blocked | One held request per user; reconnect gaps |
| SSE | Server → client | Real-time | Feeds, notifications, live scores, LLM token streams | One-way only; 6-connection limit per browser and domain on HTTP/1.1 |
| WebSocket | Both ways | Real-time | Chat, multiplayer games, collaborative editing | Stateful connections: harder load balancing, need your own reconnect and heartbeat logic |
text/event-stream; the browser reconnects automatically and sends a Last-Event-ID header so the server can resume; the server can set the reconnect delay with a retry field. MDN warns that without HTTP/2, SSE is limited to "6" open connections per browser and domain. WebSocket (RFC 6455) starts as an HTTP/1.1 request that the server answers with 101 Switching Protocols, then becomes a "two-way communication" channel where "each side can, independently from the other, send data at will".Default to SSE when only the server pushes; reach for WebSockets only when the client also streams. Either way, a fleet of stateful connections needs a pub/sub layer behind it (page 09).
Sources: WHATWG HTML · Server-sent events; MDN · EventSource; RFC 6455 (WebSocket)
Practice
—When to use what
| Situation | Pick | Why |
|---|---|---|
| Public API for partners and browsers | REST + JSON over HTTPS | Universal tooling, HTTP caching, easy to debug |
| Internal calls between your own services | gRPC + protobuf | Typed contracts, compact binary, streaming, deadlines |
| Mobile/web app with many screens over many services | GraphQL (often as a gateway) | One round trip, client picks fields |
| Feed or table with millions of rows | Cursor pagination | Constant cost per page, no skips or duplicates |
| Payments, orders, any create that must not double | POST + idempotency key | Safe retries after timeouts |
| Server pushes notifications only | SSE | Plain HTTP, auto-reconnect with Last-Event-ID |
| Chat, games, co-editing | WebSocket | Two-way, low overhead per message |
| Rare updates, simplest possible | Short polling | No held connections; fine if a delay is OK |
| Live video or voice | UDP-based (WebRTC, QUIC) | A late packet is worse than a lost one |
| Mobile users on bad networks | HTTP/3 | No TCP head-of-line blocking, survives network switches |
| Moving a site to a new host | Lower DNS TTL a day before | Caches expire quickly at switch time |
—Common mistakes
/getUser, /deleteOrder. Use nouns plus HTTP methods.200 {"error":...} breaks retries, caching and monitoring.reserved.—Interview questions
Show answer
1) The browser parses the URL and checks its caches. 2) DNS: browser/OS cache, then a recursive resolver, which (if not cached) asks root → TLD → authoritative servers and returns an IP with a TTL. 3) TCP three-way handshake (SYN, SYN-ACK, ACK) to port 443. 4) TLS 1.3 handshake, one round trip, which also checks the server's certificate. 5) HTTP request; the server (often behind a CDN and load balancer) replies. 6) The browser parses HTML and fetches CSS, JS and images, reusing the connection.
Number: a fresh HTTPS connection over TCP + TLS 1.3 costs 3 round trips before the first byte, so about 450 ms on a 150 ms ocean link. Follow-up: "How would you make it faster?" → CDN, connection reuse, HTTP/3, DNS prefetch.
Show answer
TCP is connection-based and gives a reliable, ordered byte stream with flow and congestion control. UDP sends independent datagrams with no handshake, no ordering and no resend; its header is only 8 bytes vs TCP's 20+.
Use TCP for web, APIs, databases, file transfer: anything where every byte matters. Use UDP for DNS lookups, live voice and video, games: anything where late data is useless, or where the app builds its own reliability on top, like QUIC does for HTTP/3.
Show answer
Per RFC 9110: GET, HEAD, OPTIONS, TRACE (the safe ones) plus PUT and DELETE are idempotent. POST is not; PATCH is not either (RFC 5789).
It matters because networks fail mid-request. If a request is idempotent, the client, a proxy or a load balancer can retry it after a timeout without changing the outcome. For POST we add an idempotency key to get the same guarantee.
Note: idempotent means the same effect, not the same response. A second DELETE can return 404, and that is fine.
Show answer
401: not authenticated (no or bad credentials). 403: authenticated but not allowed. 404: resource does not exist (also used to hide existence from unauthorised users). 409: conflicts with current state, such as a version mismatch or duplicate. 429: too many requests, rate limited; send Retry-After (RFC 6585). 503: service temporarily unavailable or overloaded; also often with Retry-After.
Why it matters: clients decide whether to retry by code. 4xx means "fix your request, do not retry as-is" (except 429). 5xx and 429 mean "retry later with backoff".
Show answer
A maps a name to an IPv4 address; AAAA to an IPv6 address (RFC 3596); CNAME makes a name an alias of another name, so the resolver follows it. TTL is how many seconds resolvers may cache the answer.
Trade-off: a short TTL (say 60 s) lets you fail over or migrate quickly but means more DNS queries; a long TTL (hours) means fewer lookups but slow changes. A CNAME cannot sit at the zone apex because RFC 1034 forbids other data next to a CNAME, and the apex must hold SOA and NS records.
Show answer
HTTP/1.1 is text and handles one request at a time per connection, so browsers open several connections. HTTP/2 (RFC 9113) uses binary frames and multiplexes many streams on one TCP connection, with HPACK header compression. But one lost TCP packet still stalls every stream, as RFC 9113 itself admits.
HTTP/3 (RFC 9114) runs over QUIC on UDP. QUIC orders data per stream, so a loss only delays its own stream; it merges the transport and TLS 1.3 handshakes (1 RTT, 0-RTT on resume); and connection IDs let a connection survive switching from Wi-Fi to 4G. The HTTP semantics (RFC 9110) are the same in all three.
Show answer
Cursor. The feed is huge and new posts arrive constantly. With offset, page 2 shifts when new posts land at the top, so users see duplicates or miss items, and deep offsets get slow because the database still reads all skipped rows (PostgreSQL's docs say so).
With a cursor I return an opaque next_cursor that encodes the last item's sort key, e.g. (created_at, id), and query WHERE (created_at, id) < cursor ORDER BY ... LIMIT 20 on an index. Every page costs the same. Trade-off: no jumping to page 50, which a feed never needs.
Show answer
Make additive changes without a version bump (new optional fields, new endpoints) and require clients to ignore unknown fields. Breaking changes (removing or renaming a field, changing types or meanings) get a new version.
Common options: version in the path (/v2/), simple and easy to route; or in a header, like Stripe's dated Stripe-Version, where each account is pinned to the version it started on and can upgrade per request. I would publish a deprecation timeline, log which clients still call old versions, and translate old requests at the edge so the core runs one model.
Show answer
Client sends Idempotency-Key (a UUID). Server: 1) insert (key, account, request_hash, status=in_progress) with a unique constraint; if it already exists and is done, return the stored response; if in progress, return 409; if the hash differs, return an error (Stripe does this). 2) Do the charge and store the response in the same DB transaction as the business write. 3) Expire keys after, say, 24 hours (Stripe prunes keys at least 24 hours old).
Edge cases: crash between charge and save (do both in one transaction, or make the downstream call idempotent too with the same key), and scoping keys per account so users cannot collide.
Show answer
REST for public APIs: every language and tool speaks it, and GET responses cache well in CDNs and browsers. gRPC for internal service-to-service calls: protobuf contracts generate typed clients, binary messages are small, it supports streaming and deadlines, and it runs on HTTP/2. But browsers need gRPC-Web via a proxy. GraphQL for client-driven apps that combine data from many services: one round trip, exact fields; the costs are harder caching, N+1 resolver queries and the need for query-cost limits.
A common real setup: GraphQL or REST at the edge, gRPC inside.
Show answer
Short polling: client asks every N seconds; simple but wasteful and up to N seconds late. Long polling: the server holds the request until there is data or a timeout; near real-time, works anywhere, but reconnect gaps and one held request per user. SSE: one streaming HTTP response, server to client only, built-in reconnect with Last-Event-ID; limited to 6 connections per browser and domain on HTTP/1.1 (MDN). WebSocket: full-duplex after an HTTP 101 upgrade; best for chat and games but stateful, so load balancing and reconnect logic are on you.
Rule: server-push only → SSE; both directions → WebSocket.
Show answer
TLS 1.2's full handshake takes two round trips. TLS 1.3 sends the client's key share in the first message, so the full handshake takes one round trip (RFC 8446 §2). On a 150 ms link that saves 150 ms per new connection.
0-RTT lets a returning client send data in its very first flight using a saved key. The risk is replay: an attacker can resend that early data, and the server may process it twice. So only idempotent requests like GET should be allowed in 0-RTT, never "transfer money".
Show answer
Only if the operation is idempotent, or made idempotent with a key. A timeout means "unknown outcome": the server may have done the work.
How: exponential backoff with jitter (random spread, so a million phones do not retry at the same instant), a cap on attempts, respect Retry-After on 429/503, and a total deadline. Retry 5xx and network errors; do not retry most 4xx. Server side: rate limits and load shedding so retries cannot cause a retry storm. Number to quote: without jitter, a short outage can synchronise every client into waves of traffic at the same second.
Show answer
Connections are long-lived and stateful. Problems: 1) A message for user B may arrive at node 3 while B is connected to node 17, so you need a pub/sub layer (Redis pub/sub, Kafka) or a "user → node" registry to route it. 2) Load becomes uneven: new nodes get no old connections, so rebalancing needs draining. 3) Deploys and crashes drop thousands of connections at once, causing a reconnect storm; add jittered reconnects. 4) The LB must allow long idle times and the HTTP upgrade, and use heartbeats so dead connections are noticed.
Follow-up: "How many connections per node?" → depends on memory per connection; measure, do not guess.
Show answer
Caching at many layers: the browser, the OS, the recursive resolver, and any middle resolvers each keep the old answer until its TTL expires. If the old TTL was 24 hours, some resolvers can hold it for up to a day. Some resolvers and apps also ignore or clamp TTLs, and long-lived connections (keep-alive, HTTP/2, pooled DB connections) keep talking to the old IP without a new lookup at all.
Fix: lower the TTL well before the change (at least one old TTL ahead), keep the old server running and proxying for a while, and drain long-lived connections. For instant failover, use an IP that does not change (anycast or a load balancer) instead of DNS.
Show answer
This is the N+1 problem: one query for the users, then the orders resolver runs once per user, one query each. GraphQL resolvers are per field, so this happens naturally.
Fix: batch per request with a DataLoader: collect all 50 user IDs during one tick and run SELECT * FROM orders WHERE user_id IN (...) once, then hand each user its orders. That is 2 queries instead of 51. Also add per-request caching, query depth and complexity limits so a client cannot ask for friends-of-friends-of-friends, and persisted queries to allow CDN caching.
Show answer
Use PUT /posts/{id}/likes/{userId} to like and DELETE the same URL to unlike. Both are idempotent, so double taps and retries are harmless, with no idempotency key needed. Return 204. Read the count via GET /posts/{id} with like_count, served from a cache.
Scale: likes are write-heavy for viral posts, so store the (post, user) pair as the source of truth and update counters asynchronously through a queue, possibly sharded counters for hot posts. Clients can show the like immediately (optimistic UI). Return 429 with Retry-After if a client floods. Follow-up: "Exact counts?" → eventually consistent counts are fine for likes.
Show answer
Data only flows server → client, so I pick SSE (or WebSocket if the app already has one). Score events go from the ingest service into a pub/sub topic per match. A fleet of stateless-ish "edge push" servers subscribe to topics and fan out to their connected viewers; clients connect via a load balancer and reconnect with Last-Event-ID so they do not miss scores.
Sizing: at an assumed 50k connections per server, 5M viewers need about 100 servers plus headroom. Updates are tiny and rare (a few per minute), so bandwidth is small; the hard part is connection count and reconnect storms after deploys (drain gradually, jittered reconnect). Fallback for old clients: polling a CDN-cached JSON every few seconds, which is cheap because every viewer gets the same answer.
—Cheat sheet
| Fact | Value | Source |
|---|---|---|
| DNS lookup chain | cache → resolver → root → TLD → authoritative | RFC 1034 |
| Root name servers | 13 names, 12 operators | root-servers.org |
| TCP handshake | SYN, SYN-ACK, ACK = 1 RTT | RFC 9293 |
| TLS 1.3 / TLS 1.2 handshake | 1 RTT / 2 RTT | RFC 8446 / RFC 5246 |
| QUIC handshake | 1 RTT (transport + TLS), 0-RTT on resume | RFC 9000, 9001 |
| Header sizes | UDP 8 B, TCP 20 B minimum | RFC 768, 9293 |
| HTTP RFCs | Semantics 9110, HTTP/1.1 9112, HTTP/2 9113, HTTP/3 9114 | rfc-editor.org |
| Safe methods | GET, HEAD, OPTIONS, TRACE | RFC 9110 §9.2.1 |
| Idempotent methods | safe ones + PUT, DELETE | RFC 9110 §9.2.2 |
| Not idempotent | POST, PATCH | RFC 9110, RFC 5789 |
| Rate limited | 429 + Retry-After | RFC 6585 |
| Stripe idempotency key | ≤ 255 chars, pruned after ≥ 24 h | Stripe docs |
| Stripe list limit | 1–100, default 10 | Stripe docs |
| SSE limit without HTTP/2 | 6 per browser + domain | MDN |
| WebSocket upgrade | HTTP 101 Switching Protocols | RFC 6455 |
| Protobuf field numbers 1–15 | 1 byte to encode | protobuf.dev |
—Sources
| What | Where |
|---|---|
| DNS concepts, CNAME rule | RFC 1034 |
| DNS records, TTL format | RFC 1035; AAAA: RFC 3596 |
| Root servers | root-servers.org |
| TCP, three-way handshake | RFC 9293 |
| UDP | RFC 768 |
| TLS 1.3 handshake, 0-RTT | RFC 8446 (replaced by RFC 9846, July 2026, same TLS 1.3 handshake); TLS 1.2: RFC 5246 |
| QUIC, connection migration, HOL | RFC 9000; RFC 9001 |
| HTTP semantics, methods, status codes | RFC 9110 |
| HTTP/1.1, HTTP/2, HTTP/3 | RFC 9112; RFC 9113; RFC 9114 |
| Header compression | RFC 7541 (HPACK); RFC 9204 (QPACK) |
| PATCH, 429 | RFC 5789; RFC 6585 |
| Idempotency keys | Stripe · Idempotent requests |
| Cursor pagination, versioning | Stripe · Pagination; Stripe · Versioning |
| OFFSET cost | PostgreSQL · LIMIT and OFFSET |
| gRPC | grpc.io core concepts; gRPC over HTTP2; gRPC-Web |
| Protocol Buffers | protobuf.dev proto3 guide |
| GraphQL | GraphQL spec; DataLoader |
| SSE | WHATWG HTML; MDN EventSource |
| WebSocket | RFC 6455 |