Beginner System design concept · How Systems Actually Work · 22 mins read
API Design & Communication
Learn how request-response contracts, safe retries, and bounded result sets make APIs usable in production.
HTTP
Understand how HTTP works as the request/response backbone of the web, and how to reason about methods, status codes, and protocol versions in system design.
Intuition
Almost every API you will design rides on HTTP, yet many engineers treat it as invisible plumbing. They pick methods randomly ('everything is a POST'), ignore status code semantics, and never think about which HTTP version their service speaks — then get surprised by head-of-line blocking, broken caching, or idempotency bugs that charge a customer twice. HTTP is the contract layer of the internet. Its semantics — statelessness, idempotent methods, status code families, cache headers — are what make intermediaries like CDNs, load balancers, and browser caches possible. Designing APIs that respect HTTP semantics gives you free performance and reliability; ignoring them makes your system fight the web's infrastructure instead of using it.
Mental Model
HTTP is a stateless, text-based (HTTP/1.1) or binary (HTTP/2+) request/response protocol layered on a reliable transport. A client opens a connection, sends a request line with a method, path, and headers, and the server replies with a status code, headers, and a body. Because each request is independent, servers do not remember you between calls — authentication tokens and cookies carry identity. Every layer of the web stack (browsers, CDNs, proxies, gateways, services) is built to exploit these semantics. Think of it like: HTTP is like a postal system with strict forms. Each letter (request) must contain a full address, a declared action ('please read', 'please update'), and a return address. The post office (servers, proxies) does not remember your previous letters — every envelope must stand alone. The standardized form is why letters can be sorted, cached, forwarded, and handled by millions of independent post offices without coordination.
Building Blocks
- Request/Response Model: HTTP is strictly client-initiated: the client sends a request, the server sends one response, and the exchange ends. There is no server push in classic HTTP (that requires WebSockets, SSE, or HTTP/2 push, which was largely abandoned). This simple model is what lets intermediaries cache, retry, and route traffic without application knowledge.
- Methods and Semantics: HTTP methods declare intent. GET reads and is safe + idempotent + cacheable. POST creates or triggers processing and is neither. PUT replaces a resource at a known URL and is idempotent. PATCH applies a partial update (not guaranteed idempotent). DELETE removes a resource and is idempotent. These semantics tell intermediaries when it is safe to retry or cache: a network can safely replay a GET or PUT, but replaying a POST can double-charge a card.
- Status Code Families: Status codes compress outcomes into machine-readable families: 2xx success (200 OK, 201 Created, 204 No Content), 3xx redirects (301 Moved Permanently, 304 Not Modified — the backbone of HTTP caching), 4xx client errors (400 Bad Request, 401 Unauthenticated, 403 Forbidden, 404 Not Found, 429 Too Many Requests), and 5xx server errors (500, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout). Load balancers, monitoring, and retry logic all key off these families.
- Headers: Headers carry metadata that steers behavior: Content-Type and Accept negotiate formats, Cache-Control and ETag drive caching, Authorization carries credentials, Location points to created resources, Retry-After tells clients when to come back. Well-designed APIs use headers instead of inventing custom body fields, because infrastructure already understands them.
- Statelessness: Each request must contain everything the server needs — no server-side memory of previous requests is assumed. This is the property that lets any request hit any replica behind a load balancer, which is the foundation of horizontal scaling. State lives in databases, caches, and client-held tokens (JWTs), not in the web server.
- TLS (HTTPS): HTTPS is HTTP over a TLS-encrypted channel, providing confidentiality, integrity, and server authentication via certificates. Modern systems terminate TLS at a load balancer or CDN edge, then re-encrypt or use mTLS inside the data center. TLS adds a handshake round trip (reduced by TLS 1.3's 1-RTT and 0-RTT resumption) but is non-negotiable for anything carrying user data.
Definitions
- Idempotency
-
An operation is idempotent if performing it multiple times has the same effect as performing it once.
- GET, PUT, and DELETE are idempotent by spec; POST and PATCH are not guaranteed to be.
- Idempotency is what makes automatic retries safe — a timeout on a PUT can be replayed blindly.
- For non-idempotent operations like payments, add idempotency keys (e.g., Stripe's Idempotency-Key header) so the server can deduplicate retries.
- Safe Method
-
A method that does not modify server state — in practice, GET and HEAD.
- Safe methods can be prefetched by browsers and cached by CDNs without fear.
- A GET that mutates state (e.g., /deleteUser?id=5) is a classic bug: web crawlers and prefetchers will trigger it.
- Safety is a promise to intermediaries; breaking it breaks caches and retry logic invisibly.
- Multiplexing
-
Sending multiple concurrent request/response streams over a single connection, as in HTTP/2.
- HTTP/1.1 pipelines poorly, so browsers open ~6 parallel connections per host as a workaround.
- HTTP/2 splits messages into frames tagged with stream IDs, so one connection can carry hundreds of interleaved streams.
- Multiplexing removes application-level head-of-line blocking, though TCP-level head-of-line blocking remains (fixed by HTTP/3).
- Head-of-Line Blocking
-
When one delayed message stalls all messages behind it in the same channel.
- In HTTP/1.1, a slow response blocks the next request on that connection.
- In HTTP/2 over TCP, a single lost TCP packet stalls all streams on the connection until retransmission arrives.
- HTTP/3 (over QUIC/UDP) gives each stream independent delivery, so one lost packet only stalls its own stream.
- Connection Reuse (Keep-Alive)
-
Keeping a TCP/TLS connection open across multiple requests instead of paying a new handshake each time.
- A fresh HTTPS connection costs a TCP handshake (1 RTT) plus TLS handshake (1-2 RTTs) — significant on high-latency links.
- HTTP/1.1 keep-alive and HTTP/2's single multiplexed connection exist specifically to amortize this cost.
- At the server side, millions of idle keep-alive connections consume memory and file descriptors, which is why connection limits and timeouts matter at scale.
- HTTP Caching
-
Storing responses so future requests can be served without hitting the origin server.
- Cache-Control directives (max-age, no-store, private/public) declare cacheability; ETag and If-None-Match enable cheap revalidation.
- A 304 Not Modified response sends no body, saving bandwidth when the client's copy is still fresh.
- Only safe, idempotent methods with cacheable status codes should be cached — caching a personalized response with the wrong headers leaks data between users.
Patterns
- Idempotency Keys for Mutations — Any POST that charges money, creates orders, or performs a non-reversible action where client retries are possible.
- Cache-Friendly Read APIs — Read-heavy endpoints serving shared, slowly-changing data (catalogs, feeds, public profiles).
- Correct Status Codes as Control Signals — Always — status codes are how load balancers, clients, and observability tools understand your system.
- TLS Termination at the Edge — Any public-facing service where you want centralized certificate management and cheap L7 routing.
Strategies
- Choose the Right HTTP Version Per Hop When: When designing client-to-edge and edge-to-origin paths with different latency and control needs. How: Use HTTP/3 (or HTTP/2) from mobile clients to the CDN edge to survive packet loss and connection setup costs; use HTTP/2 or even HTTP/1.1 inside the data center where networks are reliable and tooling is mature. Gateways translate between versions. Example: Cloudflare and Google front-ends speak HTTP/3 to browsers but often proxy to origins over HTTP/1.1 or HTTP/2, getting QUIC's mobile benefits without rewriting backends.
- Design Mutations Around Idempotency When: Whenever a client can retry a state-changing request after a timeout. How: Prefer PUT/PATCH with client-supplied resource IDs so retries naturally converge. For POST, require an idempotency key, store the first response against that key, and replay it on duplicates. Example: An order creation API accepts a client-generated order UUID; a retried POST returns the existing order instead of creating a duplicate.
- Exploit HTTP Caching Deliberately When: Read-heavy systems where origin load and latency dominate cost. How: Partition responses into public/shared vs private/user-specific, assign explicit Cache-Control and ETag headers, and design URLs so that changing content changes the URL (cache-busting via content hashes). Example: Static JS bundles ship with a content-hash filename and max-age=1year, while HTML is no-cache with an ETag — deploys roll out instantly without stale assets.
- Amortize Connection Costs When: High-fan-out services making thousands of downstream calls per second. How: Maintain connection pools with keep-alive to downstream services, bound pool size to protect the downstream, and set idle timeouts shorter than the server's to avoid racing closed connections. Example: A checkout service pools 200 keep-alive connections to the inventory service, cutting per-call latency by ~40ms of TCP+TLS setup on every request.
- Use Status Codes to Drive Retry Policy When: Any client or service mesh implementing automatic retries. How: Retry 5xx and 429 (with backoff, honoring Retry-After); never retry 4xx except 408/429, since the request itself is invalid; treat timeouts as unknown outcomes and apply idempotency safeguards. Example: A payment client retries a 503 twice with exponential backoff but surfaces a 402 immediately — retrying cannot fix insufficient funds.
HTTP/1.1 vs HTTP/2 vs HTTP/3
HTTP/1.1 is text-based and strictly ordered: one outstanding request per connection, so browsers open ~6 parallel TCP connections per host and developers invented hacks like domain sharding, sprites, and bundling. HTTP/2 (2015) keeps the same semantics but switches to a binary framing layer: many streams multiplexed over one TCP connection, plus header compression (HPACK) that helps when cookies and headers dominate small requests. Its weakness is TCP head-of-line blocking — one lost packet freezes every stream. HTTP/3 replaces TCP with QUIC over UDP: streams are independent, the handshake combines transport and TLS 1.3 into 1 RTT (0-RTT on resumption), and connections survive network switches via connection IDs — a phone moving from Wi-Fi to LTE keeps its connection. Practical guidance: enable HTTP/2 or HTTP/3 at the edge via your CDN/LB (usually a config flag), keep HTTP/1.1 or gRPC's HTTP/2 internally, and do not build domain-sharding hacks — they actively hurt HTTP/2.
Tradeoffs
| Decision | Upside | Downside |
|---|---|---|
| HTTP/2 vs HTTP/3 | HTTP/2 is universally supported, debuggable, and great inside data centers. HTTP/3 eliminates TCP head-of-line blocking and reconnects instantly on network changes — a big win for mobile. | HTTP/2 still stalls all streams on packet loss. HTTP/3 needs UDP allowed through firewalls, burns more CPU on encryption in user space, and has less mature debugging tooling. |
| Aggressive Caching vs Always-Fresh Data | Caching slashes origin load and latency, and CDNs make it nearly free for shared content. | Staleness bugs, cache-invalidation complexity, and the risk of leaking private data through misconfigured public caches. Every cache is a consistency tradeoff. |
| PUT with Client-Generated IDs vs POST with Server IDs | PUT + client IDs gives natural idempotency and safe retries. POST + server IDs keeps URL authority on the server and is simpler for clients. | Client IDs require UUID discipline and expose ID generation to abuse. Plain POST needs an idempotency-key layer, which adds storage and complexity on the server. |
Real World
| System | How it's used |
|---|---|
| Stripe | Accepts an Idempotency-Key header on POST /v1/charges; retries with the same key return the original charge object, making client retries over flaky networks safe for money movement. |
| Cloudflare | Terminates HTTP/3 and HTTP/2 at its edge network in 300+ cities, then proxies to origins over HTTP/1.1 or HTTP/2 — giving customers QUIC's mobile performance with zero backend changes. |
| Created QUIC and deployed HTTP/3 across Search and YouTube; Google reported roughly 8% faster search latency on desktop and lower rebuffer rates on YouTube after enabling QUIC. | |
| GitHub | Uses precise status codes in its REST API: 304 with ETag-based conditional requests so API clients can poll without burning rate limits, and 429/403 with Retry-After for rate limiting. |
| Netflix | Serves video segments over HTTP/1.1-range requests from its Open Connect CDN appliances; each segment is a cacheable GET, so 95%+ of playback bytes never touch Netflix's AWS control plane. |
Interview
Questions interviewers ask
- Explain the difference between GET, POST, PUT, and DELETE. Which are idempotent and why does that matter?
- What happens when you type a URL into a browser — walk through the HTTP exchange.
- Compare HTTP/1.1, HTTP/2, and HTTP/3. What problem does each solve?
- How would you design an API endpoint for payments to be safe against client retries?
What a strong answer covers
Candidate should explain the stateless request/response model, method semantics with idempotency, status code families with concrete codes, and the HTTP/1.1 → 2 → 3 evolution framed as solving parallelism and head-of-line blocking. Bonus: connection costs, TLS handshake overhead, and how caching semantics (ETag, Cache-Control) reduce load.
Common traps
- Claiming GET is idempotent but then designing a GET that mutates state.
- Saying HTTP/2 'fixes head-of-line blocking' without distinguishing application-level (fixed) from TCP-level (not fixed — that is HTTP/3).
- Treating status codes as decoration — retrying 4xx or ignoring 429/Retry-After.
- Forgetting that statelessness is the enabler of horizontal scaling, not just a quirk.
Quiz
A client POSTs a payment request and the connection times out. Why is blindly retrying dangerous, and what fixes it?
- POST is not idempotent, so a retry may charge twice; an idempotency key lets the server deduplicate
- POST is encrypted, so retries corrupt TLS; switching to HTTP/2 fixes it
- Timeouts always mean the server failed; retrying is always safe
- POST responses are cached, so the retry returns stale data; add Cache-Control
A timeout means the outcome is unknown — the server may have processed it. Since POST is not idempotent, retries need an explicit idempotency key so the server can return the original result.
Which status code family should an automatic retry policy generally NOT retry (other than 408/429)?
- 5xx server errors
- 3xx redirects
- 4xx client errors
- 2xx successes
4xx means the request itself is invalid (bad payload, missing auth, wrong resource) — retrying the same request will fail identically. 5xx and 429 are transient and retryable with backoff.
What key problem does HTTP/2 introduce that HTTP/3 solves?
- HTTP/2 cannot encrypt traffic, HTTP/3 adds TLS
- HTTP/2 multiplexes streams over one TCP connection, so one lost packet stalls all streams; HTTP/3 over QUIC gives streams independent delivery
- HTTP/2 is text-based and slow to parse, HTTP/3 is binary
- HTTP/2 does not support headers, HTTP/3 adds them
HTTP/2 fixed application-level head-of-line blocking via multiplexing, but all streams share one ordered TCP byte stream — a single lost packet blocks everything. QUIC moves streams into the transport so loss only affects one stream.
Why is HTTP's statelessness crucial for horizontal scaling?
- It makes requests smaller, so servers process them faster
- It allows any request to be handled by any replica behind a load balancer, since no server memory of prior requests is required
- It removes the need for TLS certificates on every server
- It guarantees responses are cacheable by CDNs
State lives in databases, caches, and client tokens rather than the web server, so load balancers can route each request to any healthy replica — the core of horizontal scale-out.
Your API serves a public product catalog read 50,000 times/second but updated hourly. Which HTTP feature gives the biggest win?
- Switching all endpoints from GET to POST for better throughput
- Enabling HTTP/1.1 pipelining on the origin server
- Increasing the TLS session timeout
- Cache-Control with max-age plus ETag, letting CDNs serve most traffic at the edge
Read-heavy, slowly-changing, shared data is the ideal caching case: edge caches absorb the vast majority of requests, and ETag revalidation makes freshness checks cheap when the TTL expires.
TCP vs UDP
Learn the reliability/speed tradeoff between TCP and UDP, when each wins, and how QUIC reshapes the choice for modern internet systems.
This section is part of the full PRISM roadmap, with worked examples, trade-off tables, interview questions and a quiz.
Unlock the full lessonREST
Learn to design resource-oriented REST APIs with correct HTTP semantics, and to recognize when REST is the wrong tool for the job.
This section is part of the full PRISM roadmap, with worked examples, trade-off tables, interview questions and a quiz.
Unlock the full lessongRPC
Learn how gRPC combines Protocol Buffers contracts with HTTP/2 to deliver fast, schema-first service-to-service communication, and when to pick it over REST.
This section is part of the full PRISM roadmap, with worked examples, trade-off tables, interview questions and a quiz.
Unlock the full lessonGraphQL
Learn how GraphQL's client-driven query language solves over- and under-fetching, what it costs (N+1 queries, caching, rate limiting), and when to avoid it.
This section is part of the full PRISM roadmap, with worked examples, trade-off tables, interview questions and a quiz.
Unlock the full lessonPractice api design & communication in PRISM
Concepts stick when you watch them fail. Build an architecture that depends on api design & communication, push traffic through it in the PRISM simulator, and see the latency and error rates change as you adjust the design.