Typing a URL triggers: DNS resolution → TCP/TLS connection → HTTP request → server response → browser parses HTML, builds the DOM/CSSOM, runs JS, fetches subresources, renders, and paints. Underneath: the client-server model over HTTP, DNS, IP routing, and TCP/IP.
Category
Networking
HTTP, caching, CORS, WebSockets, HTTP/2 vs HTTP/3.
21 questions
Browser request flow: DNS lookup (recursive resolver → root → TLD → authoritative) → TCP handshake (3-way SYN/SYN-ACK/ACK) → TLS handshake (ClientHello/ServerHello/cert/key exchange) → HTTP request → response. HTTP/2 and HTTP/3 (QUIC) collapse some of this. Front-end implications: preconnect/dns-prefetch, HTTP/2 multiplexing, certificate reuse, edge POPs.
HTTP headers carry metadata about the request and response: who's sending it (User-Agent), what content is acceptable (Accept, Accept-Language), auth (Authorization, Cookie), caching directives (Cache-Control, ETag, If-None-Match), content negotiation (Content-Type, Content-Encoding), CORS (Origin, Access-Control-Allow-*), security (CSP, HSTS, X-Frame-Options), and tracing. Headers steer routing, caching, security policy, and content selection without touching the body.
fetch is the browser-native HTTP API: lean, promise-based, but bare-bones — you handle JSON parsing manually, errors don't reject on 4xx/5xx, no timeout built in, no XHR-based progress events. Axios is a third-party library wrapping XHR (Node: http) with auto JSON, interceptors, request/response transforms, automatic 4xx/5xx rejection, timeout, cancellation, and node+browser symmetry. Use fetch when you want zero dependencies and modern features (streams, AbortController); axios when you want batteries-included DX, interceptors, and consistent behavior.
Axios is a promise-based HTTP client. Interceptors are hooks that run on every request before it's sent or every response before it resolves — used for attaching auth tokens, logging, global error handling, token refresh, and loading indicators. They centralize cross-cutting HTTP concerns.
RTK Query is Redux Toolkit's built-in data-fetching + caching layer. axios/fetch are transport — you still write loading/error state, cache, dedup, refetch logic yourself. RTKQ generates Redux slices + React hooks from endpoint definitions: free caching, automatic refetch, tag-based invalidation, request dedup, polling, optimistic updates, normalized cache. Use RTKQ if you're already on Redux; otherwise React Query/SWR are equivalent libraries with the same model and no Redux dependency.
CORS (Cross-Origin Resource Sharing) is the browser mechanism that decides whether a page on origin A is allowed to read responses from origin B. The browser sends the request, but won't expose the response to JS unless the server returns the right Access-Control-Allow-* headers. For non-simple requests (custom headers, methods like PUT/DELETE) the browser first sends an OPTIONS 'preflight' to ask permission. CORS is a browser-only enforcement — server-to-server calls bypass it entirely.
CORS preflight: for non-simple cross-origin requests (PUT, DELETE, custom headers, JSON Content-Type), the browser sends an OPTIONS request first asking 'is this method+headers allowed from this origin?' Server must respond with matching Access-Control-Allow-* or the real request never fires. SameSite cookies (Lax/Strict/None) control when cookies attach to cross-site requests — Lax is the modern default and blocks most CSRF; None requires Secure. Together they cover two different attack surfaces: CORS protects responses, SameSite protects cookies.
Cache-Control directs *how long* responses are cached. ETag/Last-Modified enable *revalidation* — client sends If-None-Match, server returns 304 if unchanged. Combine: long max-age for hashed assets, short max-age + revalidation for HTML/API.
REST: multiple endpoints, fixed response shapes, prone to over/under-fetching, easy HTTP caching. GraphQL: one endpoint, client specifies exactly the fields it needs, great for complex/nested data, but caching and rate-limiting are harder. Neither is universally better.
REST: resources at URLs, HTTP verbs for actions (GET safe/idempotent, POST create, PUT/PATCH update, DELETE), status codes (2xx/3xx/4xx/5xx) with semantic meaning, JSON bodies, versioning (URL or header), pagination (cursor preferred), filtering/sort via query params, HATEOAS (rarely fully adopted). Idempotency keys for unsafe retries.
Wrap fetch with AbortController: schedule abort after N ms, optionally forward an external signal for user-initiated cancel, clearTimeout in finally. On modern runtimes use AbortSignal.timeout and AbortSignal.any to compose. Surface a meaningful TimeoutError vs AbortError so callers can react differently (retry on timeout, don't retry on user cancel).
Create an AbortController, pass controller.signal to fetch, call controller.abort() to cancel. The fetch rejects with an AbortError you should catch and ignore. In React, abort in the useEffect cleanup to kill stale requests and prevent setState-after-unmount and out-of-order responses.
Two solid approaches: (1) AbortController — store the controller in a ref, abort it before each new request, pass signal to fetch; (2) request-id counter — bump a ref on each call, ignore responses whose id != current. Approach 1 actually cancels the network call (server stops sending); approach 2 just discards the result. Best combined: abort + id-check guards against late stragglers. React Query / SWR handle this automatically.
AbortController is the standard mechanism: create a controller, pass controller.signal to fetch, call controller.abort() to cancel. In React, store the controller in a ref or use the effect cleanup. Abort fires AbortError on the catch. For race protection (responses out of order), combine abort with a request-id guard. React Query / SWR handle this automatically when the query key changes.
Prevent hitting limits (debounce, dedup, cache, batch), respect 429s and Retry-After with exponential backoff + jitter, queue or throttle outgoing requests client-side, degrade gracefully in the UI, and surface clear feedback rather than silent failures.
Proxy through your backend (which owns the provider key, retries, and quotas), respect 429/Retry-After with exponential backoff + jitter, handle AI-specific failures (timeouts, mid-stream drops, content filtering, context-length errors), degrade gracefully, and surface clear feedback with cost controls.
Rate limits: queue with priority, respect Retry-After headers, exponential backoff + jitter, per-user quotas, circuit breaker. Large payloads: stream responses (SSE/HTTP chunked), enforce input token budgets, chunk huge inputs, use cheaper models for prefilter then expensive for refine, summarize/compress conversation history, return partial results, page through outputs. Always proxy via your server (never API keys client-side), cache deterministic queries, batch where API supports it.
HTTP is request/response — client must ask. Polling every N seconds wastes bandwidth, CPU, and battery, and adds latency up to N. WebSockets open a persistent full-duplex TCP connection so the server can push the moment something changes — near-zero latency, no per-tick overhead, one TCP+TLS handshake amortized across the session. Use WS for chat, presence, collab, live prices, notifications. Use HTTP/SSE for one-way streams; HTTP/long-poll only as a fallback.
WebSockets for bidirectional, low-latency interaction (chat, collab, presence); SSE for one-way server→client streams (notifications, feeds, live status) — simpler, auto-reconnecting, over HTTP; polling as the low-effort fallback. Choose by directionality, then handle reconnection, resync, and scale.
Polling is simplest (HTTP). SSE is one-way server→client (text, auto-reconnect, HTTP/2-friendly). WebSockets are bidirectional, binary-capable, lowest-latency. Pick the *least* powerful that fits — operational cost rises with each.