02 Β· CS Fundamentals

Networking β€” the life of a request through a stack you operate

You run Kong in front of FastAPI services on Kubernetes. Every concept below is something a packet crosses on its way through your infrastructure. The interview move: answer the theory, then land it with "and this is exactly what happens at my gateway."

01The journey: you type https://api.example.com/users/42 and hit Enter

Step 0 β€” Layers (30-second mental model)

  L7  Application   HTTP, gRPC, DNS, TLS handshake payloads   "what to say"
  L4  Transport     TCP, UDP β€” ports, reliability              "which app, delivered how"
  L3  Network       IP β€” addressing, routing between networks  "which machine"
  L2  Link          Ethernet/WiFi β€” same-network hop           "next hop"

Each layer wraps the one above (encapsulation): your JSON rides in an HTTP message, in TCP segments, in IP packets, in Ethernet frames.

Step 1 β€” DNS: name β†’ IP

Nobody routes on names. The resolver chain:

 browser cache β†’ OS cache β†’ recursive resolver (ISP/1.1.1.1/CoreDNS)
                                   β”‚ (on miss, walks the hierarchy)
                                   β–Ό
   root servers ─► .com TLD servers ─► example.com's authoritative NS
                                   β”‚
                                   β–Ό            A / AAAA record + TTL
                              203.0.113.7
  • Record types worth knowing: A (IPv4), AAAA (IPv6), CNAME (alias), NS, MX, TXT, SRV.
  • TTL governs caching β€” why DNS changes "propagate" slowly and why low TTLs are the standard trick before a migration.
  • DNS classically runs on UDP port 53 (single question, single answer β€” no connection needed), TCP for big responses/zone transfers, and increasingly DoH/DoT.
K8s connection: in-cluster DNS is CoreDNS. my-svc.my-namespace.svc.cluster.local resolves to a Service's ClusterIP; headless Services return pod IPs directly. Your services find Postgres/Kafka/each other through this exact mechanism.

Step 2 β€” TCP three-way handshake

You have an IP. Now establish a reliable byte pipe to port 443:

 Client                          Server
   β”‚ ── SYN  (seq=x) ─────────────► β”‚   "let's talk, my starting seq is x"
   β”‚ ◄─ SYN-ACK (seq=y, ack=x+1) ── β”‚   "sure, mine is y, I heard you"
   β”‚ ── ACK  (ack=y+1) ───────────► β”‚   "I heard you too" β†’ ESTABLISHED
  • 1 round trip before any data. This is why connection reuse matters: keep-alive, DB connection pools, Kong's upstream keepalive pool β€” all exist to amortize handshakes.
  • A connection = the 4-tuple (src IP, src port, dst IP, dst port). Server listens on one port; each accepted connection is a new socket/FD.
  • Teardown: FIN/ACK each way; closer lingers in TIME_WAIT (why mass short-lived connections can exhaust ephemeral ports).
  • SYN flood: attacker sends SYNs, never ACKs, fills the half-open backlog β€” mitigated by SYN cookies.

Step 3 β€” TLS handshake

On top of the TCP pipe, negotiate encryption (TLS 1.3, one round trip):

 ClientHello  ──► supported ciphers + client key share (+ SNI: hostname!)
 ServerHello ◄──  chosen cipher + server key share + certificate
      [both derive session keys via ECDHE β€” forward secrecy]
 Finished ⇄ Finished β†’ encrypted application data
  • Certificate proves identity: server's public key signed by a CA the client trusts (chain of trust). Client checks signature chain, hostname match, expiry.
  • Asymmetric crypto is only for the handshake (key exchange/authentication); the session itself uses fast symmetric encryption (AES-GCM/ChaCha20) with the derived keys.
  • SNI: the client names the host in ClientHello so one IP can serve many certs β€” this is how Kong/any ingress routes TLS for multiple domains.
  • TLS termination: in your world, TLS usually ends at the ingress/gateway (Kong); inside the cluster traffic is plaintext or re-encrypted via mTLS (service mesh). Offloads crypto and centralizes cert management (cert-manager + Let's Encrypt).

Step 4 β€” HTTP request/response

 GET /users/42 HTTP/1.1
 Host: api.example.com
 Authorization: Bearer eyJ...
 Accept: application/json
                                   ◄── HTTP/1.1 200 OK
                                       Content-Type: application/json
                                       Cache-Control: max-age=60
                                       {"id": 42, "name": "..."}

Then in your infra: request hits the LB β†’ Kong (authn plugin checks the JWT, rate-limit plugin counts it, route matches /users β†’ upstream service) β†’ kube-proxy/Service picks a pod β†’ uvicorn β†’ FastAPI handler β†’ Postgres β†’ response back down the chain, each layer adding headers (x-request-id, CORS...).

Step 5 β€” Rendering (one sentence for completeness)

Browser parses HTML, discovers CSS/JS/img URLs, repeats this whole dance per origin (with connection reuse), builds DOM+CSSOM, renders. For an API interview, DNS→TCP→TLS→HTTP is the meat.

Total cost accounting (why latency budgets die): DNS (cached: ~0) + 1 RTT TCP + 1 RTT TLS + 1 RTT request/response = ~3 RTTs cold. Cross-continent RTT ~150 ms β†’ ~450 ms before your code runs. Hence: keep-alives, CDNs/edge, HTTP/3 0-RTT resumption, and regional deployment.

🚦 The URL Journey Stepper

Step through what actually happens between Enter and pixels β€” watch the latency bill pile up before your handler runs.

DNS lookup
β†’
TCP handshake
β†’
TLS 1.3
β†’
HTTP request
β†’
server processing
β†’
response render

Press Step to fire the first packet.

02TCP vs UDP

TCP β€” reliable byte pipe

connect() ── 3-way handshake ──► pipe
send("hel"); send("lo")
  arrives in order, retransmitted
  on loss, congestion-controlled β€”
  but it's ONE byte stream, not
  messages: recv() may return "he"
HTTP, Postgres, Kafka, Redis, gRPC

UDP β€” fire and forget

sendto(datagram, addr)   no setup
  each datagram independent;
  may drop, reorder, duplicate β€”
  and that's YOUR problem (or
  no problem at all)
DNS, QUIC/HTTP-3, video/games,
syslog, StatsD
TCPUDP
ModelConnection, ordered byte streamConnectionless datagrams
ReliabilityACKs + retransmit, dedupeNone β€” fire and forget
OrderingGuaranteed in-order deliveryNone
Flow/congestion controlYes (windows, cwnd, slow start)No
OverheadHandshake, state, head-of-line blockingMinimal, no setup
Used byHTTP/1.1&2, Postgres, Kafka, Redis, gRPCDNS, QUIC/HTTP-3, video/games, syslog, StatsD

Two TCP subtleties that make you sound experienced:

  • It's a byte stream, not messages. One send() β‰  one recv(). Application protocols must frame messages themselves (HTTP's Content-Length/chunked, Kafka's length-prefixed binary protocol, Postgres's wire protocol). "Why did my socket read return half a message" = this.
  • Congestion control: TCP probes for bandwidth (slow start: cwnd doubles per RTT, then AIMD; loss β†’ back off). It's why throughput ramps up on long transfers and why a lossy link crushes TCP throughput.
UDP's pitch: when you'd rather handle loss yourself or not at all β€” a lost DNS query is just re-asked; a lost video frame is better skipped than replayed late; and QUIC builds its own smarter reliability on top of UDP (next section).

03HTTP/1.1 vs HTTP/2 vs HTTP/3 β€” intuition level

 HTTP/1.1: one request AT A TIME per TCP connection
   conn1: [req A β–“β–“β–“β–“][resp A β–“β–“β–“β–“][req B ...]      β†’ browsers open 6 conns/host
                                                      (head-of-line at HTTP level)

 HTTP/2: one TCP connection, many interleaved streams (binary frames)
   conn: [A1][B1][A2][C1][B2][A3] ...               β†’ multiplexing + header
          BUT one lost TCP packet stalls ALL streams   compression (HPACK) + prio
          (head-of-line moved down to TCP)

 HTTP/3: streams over QUIC over UDP
   independent streams β€” a lost packet stalls only ITS stream.
   TLS 1.3 built in; 1-RTT (0-RTT resume) setup; connection ID survives
   IP changes (wifi→5G, connection migration).
  • 1.1: text protocol, keep-alive reuses the connection but requests are serialized per connection; pipelining existed and failed. Still everywhere server-side (simple, debuggable β€” curl -v shows it raw).
  • 2: binary framing β†’ true multiplexing over one connection, header compression, server push (deprecated in practice). Solves HTTP-level HoL but inherits TCP-level HoL: one lost segment blocks every stream behind it.
  • 3: rebuilds the transport as QUIC on UDP β€” per-stream reliability, so loss is isolated; crypto and transport handshakes merged (fewer RTTs); userspace evolution (no waiting for kernel TCP changes).
Your stack: browsers ↔ edge/Kong often h2 or h3; gateway β†’ upstream commonly HTTP/1.1 with keepalive; gRPC requires HTTP/2 (streams map to gRPC streaming RPCs) β€” an L7 proxy must speak h2 end-to-end to route it.

04REST semantics

REST = resources (nouns) identified by URLs, manipulated by a uniform verb set, stateless requests.

VerbMeaningSafe?Idempotent?
GETReadyesyes
HEADRead, headers onlyyesyes
PUTFull replace at known URLnoyes (same PUT twice = same state)
DELETERemovenoyes
POSTCreate / non-idempotent actionnono
PATCHPartial updatenonot guaranteed
  • Safe = no state change (cacheable, prefetchable). Idempotent = N identical calls ≑ 1 call β€” this is not academic: retries and at-least-once delivery are only sane against idempotent operations. It's why payment APIs use idempotency keys on POST, and the same reasoning as your Kafka consumers needing idempotent handlers.
  • Stateless: every request carries what's needed (token, etc.); no server session affinity β†’ any pod can serve any request β†’ horizontal scaling and rolling deploys just work. This property is what makes K8s Services' random pod-picking correct.
  • Good REST hygiene: nouns not verbs (POST /orders, not /createOrder), plural collections, status codes over {"error": ...}-with-200, versioning strategy, pagination on collections.

05Real-time: WebSockets vs SSE vs long polling

The problem: HTTP is request→response; servers can't spontaneously talk. Three workarounds:

 Long polling:  client ──req──►(server holds until data or timeout)──resp──► repeat
                ~compatible everywhere; latency + reconnect overhead; no infra needs

 SSE:           client ──GET──► server keeps response open, streams
                "data: ...\n\n" events forever. ONE WAY (server→client).
                Plain HTTP: proxies/LBs happy, auto-reconnect built in (EventSource).

 WebSocket:     HTTP GET + Upgrade: websocket β†’ 101 Switching Protocols
                β†’ same TCP conn becomes a FULL-DUPLEX message pipe (ws:// wss://)
SSEWebSocketLong polling
DirectionServer β†’ client onlyFull duplexServer β†’ client (per request)
TransportPlain HTTP response, kept openHTTP Upgrade β†’ raw TCP framingRepeated plain HTTP
Proxy/LB friendlinessHigh (it's just HTTP)Needs Upgrade support, idle-timeout careHighest
ReconnectBuilt in (EventSource + Last-Event-ID)Roll your ownInherent (every poll)
Best forNotifications, live feeds, LLM token streamingChat, collab editing, gamesFirewall-hell fallback
SSE is how LLM streaming works — your daily reality. OpenAI-style APIs stream completions as SSE data: events over one long-lived HTTP response; that token-by-token typing effect in every AI product is an EventSource-style stream, not a WebSocket. When a server→client stream is all you need, SSE is the simplest thing that works: plain HTTP, proxy-friendly, auto-reconnecting.

Choosing:

  • Serverβ†’client stream only (notifications, live feeds, LLM token streaming) β†’ SSE.
  • Bidirectional, low latency (chat, collab editing, games) β†’ WebSocket.
  • Lowest common denominator / firewall hell β†’ long polling fallback.
Ops angle (say this): persistent connections change your scaling math β€” stateful connections pin to pods, so you need sticky routing or a pub/sub backplane (Redis pub/sub, Kafka) so any pod can push to any client; LB idle timeouts will silently kill quiet WebSockets (heartbeats/ping-pong); rolling deploys must drain long-lived connections gracefully (SIGTERM handling from the OS page).

06Load balancers: L4 vs L7

 L4 (transport):  routes on IP:port. Sees TCP/UDP, not HTTP.
   client ──TCP──► [LB: pick backend, forward/NAT packets] ──► backend
   Blazing fast, protocol-agnostic, cheap. Can't route by path/header,
   can't retry a request, one connection = one backend.

 L7 (application): TERMINATES the connection, parses HTTP, makes a new
   request upstream.
   client ══HTTP══► [LB: read path/headers/cookies] ══HTTP══► chosen backend
   Path/host routing, TLS termination, retries, header rewrites,
   per-request balancing, canary %, auth β€” at parsing + memory cost.
  • Algorithms: round-robin, least-connections, weighted, consistent hashing (nice segue: same consistent hashing that Kafka-esque systems and caches use to minimize reshuffling).
  • Health checks eject bad backends β€” the LB-level mirror of K8s readiness probes.
In your cluster: kube-proxy/Service = L4 (iptables/IPVS conntrack-based random pod pick). Ingress / Kong = L7. Cloud LB in front (NLB=L4, ALB=L7). A request may cross an L4 LB β†’ L7 gateway β†’ L4 service routing before hitting uvicorn.

07API gateway β€” what Kong actually does for you

A gateway is an L7 reverse proxy + policy engine: one front door where cross-cutting concerns live so N services don't reimplement them.

            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ KONG ────────────────────────┐
 client ──► β”‚ route match β†’ [authn: JWT/key] β†’ [rate limit] β†’      β”‚ ──► upstream
            β”‚ [transform headers] β†’ [log/metrics/tracing] β†’ proxy  β”‚     service
            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Speak concretely, since you run it:

  • Routing: Routes β†’ Services β†’ Upstreams (with its own LB + health checks across targets).
  • Auth at the edge: JWT validation / API keys / OAuth introspection before traffic ever reaches a pod β€” services can then trust identity headers.
  • Rate limiting & quotas: per-consumer/route counters (local, or Redis-backed for cluster-accurate limits β€” a distributed-counter problem, ties to system design).
  • Observability: uniform access logs, latency metrics, request-id injection for tracing across services.
  • Transformations, CORS, request size limits, canary/traffic-split, plugin ecosystem (Kong = OpenResty = NGINX + Lua/plugins).
  • Gateway (north-south, edge policy) vs service mesh (east-west, sidecar-per-pod mTLS/retries/telemetry) β€” complementary, not competitors.
Tradeoffs to volunteer: extra hop of latency, potential single point of failure (run it HA), config sprawl; but the alternative is every service reimplementing auth/limits inconsistently.

08Common ports

PortWhat
22SSH
53DNS
80 / 443HTTP / HTTPS
5432PostgreSQL
6379Redis
9092Kafka broker
9200OpenSearch/Elasticsearch HTTP
8000 / 8080dev HTTP / alt HTTP (uvicorn default 8000)
8001 / 8443Kong admin API / proxy TLS (8000 proxy HTTP)
3306 / 27017MySQL / MongoDB
6443Kubernetes API server
25 / 587SMTP

(Ports < 1024 are privileged β€” need root/CAP_NET_BIND_SERVICE, why containers often listen on 8080 and let the LB own 443.)

09Status codes that matter

  • 2xx β€” 200 OK Β· 201 Created (+ Location) Β· 202 Accepted (async job queued β€” your "enqueue to Kafka and return" pattern) Β· 204 No Content (DELETE).
  • 3xx β€” 301 permanent / 302 temporary redirect Β· 304 Not Modified (conditional GET hit: ETag/If-None-Match β€” caching's handshake) Β· 307/308 (redirect preserving method).
  • 4xx (client's fault) β€” 400 malformed Β· 401 unauthenticated (who are you) vs 403 unauthorized (I know you; no) Β· 404 Β· 405 wrong verb Β· 409 conflict (version/duplicate) Β· 422 validation failed (FastAPI's default for Pydantic errors) Β· 429 Too Many Requests (what Kong's rate limiter returns; include Retry-After).
  • 5xx (server's fault) β€” 500 unhandled error Β· 502 Bad Gateway (proxy got garbage/refusal from upstream β€” Kong couldn't reach or parse your pod's reply) Β· 503 Service Unavailable (no healthy upstream / overload β€” what you see when all pods fail readiness) Β· 504 Gateway Timeout (upstream too slow β€” proxy timeout < app timeout misconfigs live here).
Gateway-debugging instinct (gold in interviews): 502/503/504 at the edge point at the hop behind the proxy β€” crashed pods, failing readiness probes, timeout mismatches, connection-pool exhaustion β€” and your first stops are Kong logs + kubectl get endpoints.

10Status-code quiz β€” what does the client see?

1. A request hits Kong with no Authorization header at all. Which status β€” and which one would it be if the JWT were valid but the user lacked the required role?

401 Unauthorized (really "unauthenticated": who are you? β€” challenge to present credentials). With a valid JWT but insufficient permissions: 403 Forbidden (I know exactly who you are; the answer is no).

2. A consumer blows through the per-minute limit configured in Kong's rate-limiting plugin. What comes back, and which header should ride along?

429 Too Many Requests, ideally with Retry-After so well-behaved clients back off instead of hammering. This is Kong's rate limiter speaking β€” the request never reached your pod.

3. Every pod behind a Service is failing its readiness probe after a bad deploy. What does the gateway return to clients?

503 Service Unavailable β€” no healthy upstream to route to. First stops: kubectl get endpoints (empty endpoints list = readiness failing) and the probe config. This is the classic "all pods fail readiness" signature at the edge.

4. A pod crashes mid-response (or refuses the connection), and Kong gets garbage instead of a valid HTTP reply. Status?

502 Bad Gateway β€” the proxy reached (or tried to reach) the upstream and got an invalid/refused response. Points at crashed processes, connection refusal, or a broken reply from the hop behind the proxy.

5. Your FastAPI handler takes 45 s on a slow Postgres query, but Kong's upstream timeout is 30 s. What does the client see, and what's the misconfiguration pattern called out here?

504 Gateway Timeout β€” upstream too slow for the proxy's budget. The classic misconfig: proxy timeout < app timeout, so the proxy gives up while the app is still working (and may even finish the work with nobody listening). Align timeout budgets outward-in.

6. A client POSTs JSON that fails Pydantic validation in FastAPI. And separately: a valid request that just enqueues a job to Kafka and returns immediately β€” what should each return?

Validation failure: 422 Unprocessable Entity β€” FastAPI's default for Pydantic errors (a 400 is also defensible; 422 says "syntactically fine, semantically invalid"). The enqueue-and-return pattern: 202 Accepted β€” "queued, not done yet," ideally with a status URL to poll.

11Self-test β€” say these answers out loud

Q1. What happens when you type a URL and press Enter?

DNS (cache β†’ recursive resolver β†’ root/TLD/authoritative, get A record with TTL) β†’ TCP 3-way handshake to :443 β†’ TLS 1.3 handshake (SNI, cert chain validation, ECDHE key exchange β†’ symmetric session keys) β†’ HTTP request β†’ server chain (LB β†’ gateway β†’ service β†’ app β†’ DB) β†’ response β†’ render. ~3 RTTs cold before any app work, hence keep-alive, CDNs and HTTP/3.

Q2. TCP vs UDP β€” and why does anything use UDP?

TCP: connection, ordered reliable byte stream, flow+congestion control β€” the default for request/response. UDP: connectionless datagrams, no guarantees, no handshake β€” for cases where retransmission is pointless (live media), trivial (DNS re-ask), or where you build better reliability yourself (QUIC/HTTP-3). Bonus: TCP is a byte stream, so protocols must do their own message framing.

Q3. Difference between HTTP/1.1, 2 and 3?

1.1: one request at a time per connection (parallelism = many connections). 2: binary frames multiplex many streams over one TCP connection + header compression β€” but TCP-level head-of-line blocking remains (one lost packet stalls all streams). 3: QUIC over UDP β€” independent per-stream loss recovery, TLS integrated, fewer handshake RTTs, connection migration across network changes. gRPC needs h2.

Q4. What do idempotent and safe mean, and why do they matter?

Safe = no state change (GET/HEAD) β†’ cacheable. Idempotent = repeating has no additional effect (GET/PUT/DELETE; not POST). Matters because retries, at-least-once queues, and network timeouts all re-execute operations β€” you can only blindly retry idempotent ones; otherwise you need idempotency keys. Same reason my Kafka consumers are written idempotent.

Q5. WebSockets vs SSE vs long polling β€” how do you choose?

Long polling: hold the request until data; works everywhere, inefficient. SSE: one long-lived HTTP response streaming events, server→client only, auto-reconnect, proxy-friendly — ideal for notifications and LLM token streaming. WebSocket: HTTP Upgrade to a full-duplex TCP pipe — for chat/collab/bidirectional. Ops cost of both persistent options: sticky/stateful connections, pub/sub backplane for multi-pod fanout, heartbeats vs idle timeouts, graceful drain on deploys.

Q6. L4 vs L7 load balancing?

L4 forwards TCP/UDP by IP:port β€” fast, dumb, connection-granular (kube-proxy/NLB). L7 terminates and parses HTTP β€” routes by path/host/header, does TLS termination, retries, canaries, per-request balancing (Kong/ALB/ingress). Real deployments stack them: NLB β†’ Kong β†’ K8s Service.

Q7. What does an API gateway do? (You run Kong.)

Single L7 front door centralizing cross-cutting policy: route matching to upstreams, edge authentication (JWT/keys), rate limiting (Redis-backed for cluster accuracy), transformations, CORS, and uniform logging/metrics/request-ids. Kong = NGINX/OpenResty + Lua plugins; Services/Routes/Upstreams model with health-checked load balancing. Tradeoffs: added hop, must be HA. Distinct from a service mesh, which handles east-west traffic via sidecars.

Q8. 401 vs 403? 502 vs 503 vs 504?

401 = not authenticated (missing/bad credentials; challenge to log in) vs 403 = authenticated but not allowed. 502 = proxy got an invalid/refused response from upstream; 503 = no available/healthy upstream or overload; 504 = upstream timed out. All three at a gateway mean "look behind the proxy": readiness probes, crashes, timeout budget mismatches.

Q9. Walk through the TCP handshake. Why does connection reuse matter?

SYN (seq x) β†’ SYN-ACK (seq y, ack x+1) β†’ ACK: one RTT to establish, then TLS adds another. At 100+ ms RTTs, per-request handshakes dominate latency and burn ports/FDs β€” hence HTTP keep-alive, connection pools to Postgres/Redis, and gateway upstream keepalives.

Q10. How does service discovery/networking work inside Kubernetes?

CoreDNS resolves svc.namespace.svc.cluster.local to a Service ClusterIP; kube-proxy programs iptables/IPVS to DNAT that VIP to a ready pod IP (L4, effectively random). Every pod has its own IP (net namespace); readiness probes gate endpoint membership β€” which is exactly why a failing readiness probe manifests as 503s at the gateway.

Q11. Why is HTTPS not slow / what's actually expensive in TLS?

Handshake costs one RTT (TLS 1.3) and some asymmetric crypto; the session uses hardware-accelerated symmetric ciphers (AES-GCM) β€” negligible per-byte cost. With session resumption/0-RTT and connection reuse, TLS overhead mostly disappears; terminate at the edge to centralize cert handling.