HTTP (Hypertext Transfer Protocol) is the language browsers, mobile apps and backend services use to ask each other for things. Every web page, every REST API call, every image on a CDN travels as HTTP. If DNS tells your computer where to go, HTTP defines what to say once you get there.
For interviews, HTTP is one of the highest-yield networking topics because it connects directly to backend work. Expect questions like "what is the difference between PUT and PATCH", "is POST idempotent", "explain 401 versus 403", "how does browser caching work with ETag", "what do the SameSite and HttpOnly cookie flags do", "what does HTTP/2 improve over HTTP/1.1, and why did HTTP/3 move to UDP", and "what is CORS and why does my fetch fail". This lesson covers each of these with raw examples you can try with curl.
The request/response model
HTTP is a client-server, request-response protocol. The client (a browser, curl, a mobile app, another service) opens a connection to a server and sends a request. The server sends back exactly one response. Then the connection can carry another request or be closed.
HTTP is stateless: each request carries everything the server needs to understand it. The server does not automatically remember that the same user sent the previous request. State such as "logged in" is layered on top using cookies or tokens, covered later.
HTTP runs on top of a reliable transport: TCP for HTTP/1.1 and HTTP/2 (usually port 80 for plain HTTP and port 443 for HTTPS), and QUIC over UDP for HTTP/3. You studied TCP in the transport layer lesson.
Anatomy of a request
HTTP/1.1 messages are plain text, which makes them easy to read. Here is a request for a JSON resource:
GET /api/users/42?fields=name,email HTTP/1.1 <- request line
Host: api.example.com <- headers start
User-Agent: curl/8.7.1
Accept: application/json
Accept-Encoding: gzip, br
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
<- blank line (CRLF)
<- body (empty for GET)
The parts:
- Request line: the method (
GET), the request target (path plus optional query string,/api/users/42?fields=name,email) and the protocol version (HTTP/1.1). - Headers:
Name: valuepairs that add metadata. Header names are case-insensitive. - A blank line (carriage return plus line feed, written CRLF or
\r\n) that marks the end of the headers. - An optional body, used by methods such as POST and PUT to send data.
The Host header is mandatory in HTTP/1.1. One IP address often serves many websites (virtual hosting), and Host tells the server which site you want.
Anatomy of a response
HTTP/1.1 200 OK <- status line
Date: Sat, 10 Oct 2026 05:30:00 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 49
Content-Encoding: gzip
Cache-Control: private, max-age=60
ETag: "a1b2c3"
{"id":42,"name":"Asha","email":"asha@example.com"}
The status line has the version, a three-digit status code (200) and a human-readable reason phrase (OK). Clients act on the code, not the phrase. Headers follow, then a blank line, then the body.
How does the receiver know where the body ends?
On a persistent connection, the receiver needs to know when one message stops so the next can begin. HTTP/1.1 uses one of two methods:
Content-Length: 49: the body is exactly 49 bytes.Transfer-Encoding: chunked: the body arrives in pieces, each prefixed by its size in hexadecimal, ending with a zero-size chunk. Servers use this when they do not know the size in advance, such as when streaming a generated page.
HTTP/1.1 200 OK
Transfer-Encoding: chunked
7\r\n
Hello, \r\n
6\r\n
world!\r\n
0\r\n
\r\n
The two chunks are 7 bytes (Hello, ) and 6 bytes (world!). HTTP/2 and HTTP/3 do not need either trick: they frame data in binary with explicit lengths.
URLs in brief
A URL (Uniform Resource Locator) tells the client what to fetch and how:
https://user@shop.example.com:8443/products/shoes?size=9&color=red#reviews
\___/ \__/ \______________/ \__/\_____________/ \________________/ \_____/
scheme user host port path query fragment
- Scheme: which protocol (
http,https). It implies a default port (80 or 443). - Host and optional port.
- Path: which resource on the server.
- Query:
key=valueparameters after?, separated by&. - Fragment: after
#. It is never sent to the server; the browser uses it to scroll or for client-side routing.
Characters with special meaning are percent-encoded: a space becomes %20, & inside a value becomes %26.
Methods: safety and idempotency
The method says what you want to do with the resource. Two properties matter for interviews:
- A method is safe if it does not change server state. The client can call it freely, and crawlers and prefetchers may call it without asking. (A server may still log the request; "safe" is about the meaning of the request.)
- A method is idempotent if making the same request once or many times leaves the server in the same state. This matters for retries: if a network timeout leaves you unsure whether a request succeeded, you may safely resend an idempotent one.
| Method | Purpose | Safe | Idempotent | Has body |
|---|---|---|---|---|
| GET | Read a resource | Yes | Yes | No (not meaningful) |
| HEAD | Like GET, but headers only | Yes | Yes | No |
| OPTIONS | Ask which methods/features are allowed (used by CORS preflight) | Yes | Yes | Rarely |
| POST | Create a resource or trigger processing | No | No | Yes |
| PUT | Replace the resource at this URL with the body (create if missing) | No | Yes | Yes |
| PATCH | Partially modify a resource | No | Not guaranteed | Yes |
| DELETE | Remove the resource | No | Yes | Usually no |
Worked example: idempotency in practice
Consider a bank account with balance 1000.
PUT /accounts/7/balancewith body{"balance": 500}. First call: balance becomes 500. Second identical call: balance is still 500. Idempotent.POST /accounts/7/withdrawalswith body{"amount": 500}. First call: balance 500. Second call: balance 0. Not idempotent. A blind retry withdraws twice.DELETE /accounts/7/cards/3. First call removes card 3 and returns204. The second call might return404because the card is gone, but the server state is the same either way: no card 3. Idempotent. Idempotency is about state, not about identical responses.PATCH /counters/9with body{"op": "increment"}is not idempotent, whilePATCHwith{"name": "new"}happens to be. That is why PATCH is "not guaranteed".
Payment APIs make POST retry-safe with an idempotency key: the client sends a unique header such as Idempotency-Key: 6f1c... and the server stores the result under that key. A retry with the same key returns the stored result instead of charging again.
Interview tip
For "PUT versus PATCH", say: PUT sends the full new representation and replaces the resource, so it is idempotent; PATCH sends a set of changes, and whether repeating it is safe depends on the changes. For "PUT versus POST": the client chooses the URL with PUT, the server chooses it with POST.
Status codes
Status codes are grouped by the first digit:
| Class | Meaning | Who should act |
|---|---|---|
| 1xx | Informational: request received, continuing | Rarely seen by apps |
| 2xx | Success | Done |
| 3xx | Redirection: go elsewhere or use your cache | Client follows |
| 4xx | Client error: the request is wrong | Client must change something |
| 5xx | Server error: a valid request failed on the server | Server side; client may retry later |
The codes you must know:
| Code | Name | When to use or expect it |
|---|---|---|
| 100 | Continue | Client sent Expect: 100-continue before uploading a large body; server says go ahead |
| 101 | Switching Protocols | Upgrading the connection, for example to WebSocket |
| 200 | OK | Generic success with a body |
| 201 | Created | POST created a resource; include a Location header pointing to it |
| 202 | Accepted | Work queued, not done yet (asynchronous jobs) |
| 204 | No Content | Success with no body, common for DELETE |
| 206 | Partial Content | Answering a Range request (video seeking, resumable downloads) |
| 301 | Moved Permanently | Resource moved for good; clients and search engines update links |
| 302 | Found | Temporary redirect |
| 304 | Not Modified | Conditional request: your cached copy is still valid; no body |
| 307 / 308 | Temporary / Permanent Redirect | Like 302 / 301 but the method and body must not change |
| 400 | Bad Request | Malformed syntax or invalid input |
| 401 | Unauthorized | Not authenticated: no credentials or bad credentials |
| 403 | Forbidden | Authenticated, but not allowed |
| 404 | Not Found | No such resource (also used to hide existence from unauthorised users) |
| 405 | Method Not Allowed | Resource exists but does not support this method |
| 409 | Conflict | Conflicts with current state, for example a duplicate username or version clash |
| 412 | Precondition Failed | An If-Match check failed (optimistic locking) |
| 415 | Unsupported Media Type | Server cannot parse the Content-Type sent |
| 422 | Unprocessable Content | Well-formed but semantically invalid (common in validation errors) |
| 429 | Too Many Requests | Rate limited; check Retry-After |
| 500 | Internal Server Error | Unhandled server bug |
| 502 | Bad Gateway | A proxy or load balancer got an invalid response from the upstream server |
| 503 | Service Unavailable | Overloaded or in maintenance; temporary |
| 504 | Gateway Timeout | A proxy timed out waiting for the upstream server |
Common mistake
401 is named "Unauthorized" but really means unauthenticated ("who are you?"). 403 means unauthorised ("I know who you are, and you can't do this"). A 401 response should include a WWW-Authenticate header telling the client how to authenticate.
Another subtle pair: browsers historically changed a POST into a GET when following 301 and 302. 307 and 308 were added to forbid that change, so use 308 to permanently redirect an API endpoint that receives POSTs.
Headers
Headers carry everything that is not the resource itself. Some are general, some request-only, some response-only. The important groups follow.
Content negotiation
One URL can have several representations (formats, languages, compressions). The client states preferences; the server picks.
| Request header | Example | Server responds with |
|---|---|---|
Accept | application/json, text/html;q=0.8 | Content-Type: application/json |
Accept-Language | hi-IN, en;q=0.7 | Content-Language: hi-IN |
Accept-Encoding | gzip, br, zstd | Content-Encoding: br |
The q value (0 to 1) is a preference weight; the default is 1. In the example, the client prefers JSON (weight 1) over HTML (weight 0.8). If the server cannot satisfy Accept at all, it may reply 406 Not Acceptable, though most servers just send their default.
When the response depends on a request header, the server adds Vary (for example Vary: Accept-Encoding) so caches store separate copies per value instead of serving a Brotli-compressed body to a client that cannot decode it.
Caching
HTTP caching lets browsers, CDNs and proxies reuse responses instead of fetching them again. It is the single biggest web performance tool. Two ideas work together: freshness (how long can I use this without asking?) and validation (once stale, is my copy still good?).
Cache-Control is the main freshness header. Common directives:
| Directive | Meaning |
|---|---|
max-age=3600 | Fresh for 3600 seconds after it was generated |
s-maxage=600 | Overrides max-age for shared caches (CDNs, proxies) |
public | Any cache may store it, even if the request had credentials |
private | Only the user's browser may store it (personal data) |
no-cache | May be stored, but must revalidate with the server before every use |
no-store | Must not be stored anywhere (sensitive data) |
must-revalidate | Once stale, must not be used without successful revalidation |
immutable | Will never change while fresh; do not revalidate even on reload |
stale-while-revalidate=30 | May serve stale for 30 s while refreshing in the background |
Common mistake
no-cache does not mean "don't cache". It means "cache, but check with me every time". To forbid storage, use no-store. Interviewers ask this often.
Validators let a cache ask "has it changed?" cheaply:
ETag: an opaque identifier for one version of the resource, often a hash of the content, for exampleETag: "a1b2c3". A weak ETag,W/"a1b2c3", means "semantically equivalent" rather than byte-identical.Last-Modified: the time the resource last changed. Coarser (one-second resolution) and less reliable than ETag.
Conditional requests
When a cached copy is stale, the browser sends a conditional request carrying the validator:
GET /app.css HTTP/1.1
Host: www.example.com
If-None-Match: "a1b2c3"
If-Modified-Since: Thu, 08 Oct 2026 10:00:00 GMT
If the resource is unchanged, the server returns 304 Not Modified with no body. The browser marks its copy fresh again and uses it. If it changed, the server returns 200 with the new body and a new ETag.
Here is the full lifecycle with max-age=60:
t=0s GET /app.css --> 200, body (40 KB), ETag "v1", max-age=60
t=30s browser needs app.css --> served from cache, no network at all
t=90s copy is stale (age 90 > 60)
GET + If-None-Match "v1" --> 304 Not Modified (headers only)
cache refreshed, fresh again until t=150s
t=200s file was redeployed
GET + If-None-Match "v1" --> 200, new body, ETag "v2"
A conditional request still costs a round trip, but saves the bandwidth of the body. A fresh cache hit costs nothing.
Cache busting is the common strategy for static assets: put a content hash in the file name (app.3f9a1c.css) and serve it with Cache-Control: public, max-age=31536000, immutable. When the file changes, its name changes, so the HTML references a new URL and old caches are simply never asked for the new file. The HTML itself is served with no-cache so it is always revalidated.
The same validators enable optimistic concurrency for writes: a client sends PUT with If-Match: "v1", and the server returns 412 Precondition Failed if someone else already changed the resource. This prevents lost updates.
Other headers worth knowing
| Header | Purpose |
|---|---|
Content-Type | Media type of the body, such as application/json |
Authorization | Credentials, such as Bearer <token> or Basic <base64> |
Location | URL for a redirect or a newly created resource |
User-Agent | Client software identification |
Referer | The page that linked here (historic misspelling) |
Range / Content-Range | Request or deliver part of a resource |
Retry-After | When to retry after 429 or 503 |
Strict-Transport-Security | Force HTTPS for this site in future (HSTS) |
X-Forwarded-For / Forwarded | Original client IP, added by proxies |
Cookies, sessions and tokens
Because HTTP is stateless, the server needs a way to recognise a returning client. Cookies are small key-value pairs the server asks the browser to store and send back.
HTTP/1.1 200 OK
Set-Cookie: session_id=k8f2Lq9x; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=86400
On every later request to the same site and path, the browser sends:
Cookie: session_id=k8f2Lq9x
Cookie attributes
| Attribute | What it does | Why it matters |
|---|---|---|
Domain | Which hosts receive it; omitted means only the exact host that set it | Setting Domain=example.com shares it with all subdomains |
Path | URL path prefix that receives it | Narrows scope |
Expires / Max-Age | When it dies; without either, it is a session cookie deleted when the browser session ends | Controls login duration |
Secure | Only sent over HTTPS | Stops leakage over plain HTTP |
HttpOnly | Not readable from JavaScript (document.cookie) | Limits damage from XSS: injected script cannot steal it |
SameSite | Controls sending on cross-site requests | Main defence against CSRF |
SameSite values:
Strict: never sent on cross-site requests, not even when the user clicks a link to your site from another site. Most secure, but users arriving from an email link appear logged out.Lax: sent on top-level navigations with safe methods (clicking a link, a GET form), but not on cross-site POSTs, iframes orfetchcalls. Modern browsers treat cookies without a SameSite attribute asLaxby default (Chrome since 2020; behaviour varies slightly between browsers).None: always sent, cross-site included. Must be combined withSecure. Needed for third-party embeds.
"Cross-site" here means a different registrable domain (example.com versus evil.com). app.example.com and api.example.com are the same site, though they are different origins, a distinction that matters for CORS below.
Sessions versus tokens
Two common ways to keep a user logged in:
Server-side sessions. After login, the server creates a session record (user id, expiry) in memory, a database or Redis, and gives the browser an opaque, random session id in a cookie. Each request looks up the id.
- Easy to revoke: delete the record and the user is logged out.
- Requires shared session storage when you have many servers.
Tokens, often JWTs. After login, the server issues a signed token containing claims such as {"sub": "42", "exp": 1760083200}. A JWT (JSON Web Token) is three Base64URL parts, header.payload.signature. Any server holding the key can verify the signature without a lookup.
- Stateless verification scales easily and works across services.
- Hard to revoke before expiry, so access tokens are kept short-lived (minutes) and paired with a longer-lived refresh token that can be revoked.
- The payload is only encoded, not encrypted. Anyone can read it; never put secrets in it.
| Session id in cookie | JWT access token | |
|---|---|---|
| Server state | Session store required | None for verification |
| Revocation | Immediate | Wait for expiry or keep a denylist |
| Size | Small (about 32 bytes) | Larger (hundreds of bytes) |
| Typical client | Browsers | Mobile apps, service-to-service, SPAs |
Where to store a token in a browser is a known debate: localStorage is readable by any script on the page (XSS risk), while an HttpOnly; Secure; SameSite cookie is not readable by script but is sent automatically (so CSRF must be handled). Many teams put tokens in HttpOnly cookies for browsers.
Persistent connections and pipelining
HTTP/1.0 opened a new TCP connection per request by default. Each connection costs a TCP handshake (one round trip) and, for HTTPS, a TLS handshake (one or two more), and starts in TCP slow start (from the congestion control lesson). Loading a page with 50 resources this way is painfully slow.
HTTP/1.1 made connections persistent by default (keep-alive): after one response, the same connection carries the next request. A client sends Connection: close to end it.
Persistent connections still handle one request at a time per connection. HTTP/1.1 also defined pipelining, sending several requests without waiting for responses, but responses must come back in order. If the first response is slow, everything behind it waits: head-of-line (HOL) blocking. Proxies handled pipelining badly, so browsers disabled it.
The practical HTTP/1.1 workaround was to open about six parallel connections per host (the common browser limit) and to use tricks such as domain sharding (spreading assets over static1, static2...), sprite sheets and file concatenation. All of these are hacks around the protocol's limits.
HTTP/2
HTTP/2 (RFC 9113, originally RFC 7540 in 2015) keeps the same methods, status codes and headers, so applications barely notice. What changes is how messages travel on the wire.
Binary framing and multiplexing
HTTP/2 splits each message into binary frames (HEADERS, DATA, and control frames). Each request-response pair is a stream with a numeric id. Frames from many streams interleave on a single TCP connection:
HTTP/1.1, one connection: HTTP/2, one connection:
req1 ---------> resp1 [H1][H3][D1][H5][D3][D1][D5][D3]
req2 -> resp2 frames from streams 1, 3, 5
req3 ... interleaved; each reassembled
(one at a time, in order) independently
This is multiplexing: many concurrent requests on one connection, with no HTTP-level head-of-line blocking. A slow response no longer blocks fast ones. One connection per origin replaces six, and domain sharding becomes counterproductive.
HPACK header compression
HTTP headers are repetitive: every request to a site sends nearly the same User-Agent, Cookie and Accept lines, often hundreds of bytes each time. HPACK compresses them:
- A static table of 61 common header fields (
:method: GET,:status: 200...) referenced by index. - A dynamic table that both sides build during the connection. After a header has been sent once, later requests send just a small index.
- Huffman coding for literal strings.
HPACK was designed specifically to avoid the CRIME attack, which broke general-purpose compression (like gzip) of headers containing secrets.
Other HTTP/2 features
- Stream prioritisation: the client can hint which streams matter most. The original dependency-tree scheme was complex and inconsistently implemented; RFC 9218 defines a simpler
Priorityheader. - Flow control per stream and per connection.
- Server push: the server could send resources the client had not asked for yet (push
style.cssalong withindex.html). In practice it often pushed files the browser already had cached, wasting bandwidth. Chrome removed support in 2022, and it is considered deprecated. The replacement is the103 Early Hintsstatus, which tells the browser what to fetch while the server is still preparing the main response, plus<link rel=preload>tags.
In practice, browsers only use HTTP/2 over TLS. The protocol is chosen during the TLS handshake through ALPN (Application-Layer Protocol Negotiation), where the client offers h2 and http/1.1.
HTTP/2's remaining problem
HTTP/2 fixed HOL blocking at the HTTP layer but not at the TCP layer. TCP delivers bytes strictly in order. If one packet is lost, TCP holds back every later byte until the retransmission arrives, even bytes belonging to other, unaffected streams. On a lossy mobile network, one lost packet stalls all multiplexed streams, and HTTP/2 can perform worse than six HTTP/1.1 connections.
HTTP/3 and QUIC
HTTP/3 (RFC 9114, 2022) runs over QUIC (RFC 9000), a transport protocol built on UDP. QUIC moves into user space what TCP and TLS did in the kernel, and fixes their pain points:
- Independent streams in the transport. QUIC knows about streams. A lost packet only stalls the stream whose data it carried. Other streams keep flowing. This removes transport-level HOL blocking.
- Faster handshakes. QUIC merges the transport and TLS 1.3 handshakes: a new connection takes one round trip (versus about two for TCP plus TLS 1.3, and three for TCP plus TLS 1.2). Resuming a connection to a known server can send data in zero round trips (0-RTT), with a replay caveat.
- Always encrypted. TLS 1.3 is built in; even most transport headers are encrypted, which stops middleboxes from depending on (and ossifying) them.
- Connection migration. A QUIC connection is identified by a connection ID, not the four-tuple of IPs and ports. When your phone switches from Wi-Fi to mobile data, the connection survives instead of being re-established.
- QPACK replaces HPACK, adapted to out-of-order delivery.
Why build on UDP instead of inventing a new IP protocol? Because firewalls, NAT boxes and operating systems everywhere already pass UDP. A brand-new transport protocol would be blocked by too many devices to deploy.
Browsers discover HTTP/3 through the Alt-Svc response header (alt-svc: h3=":443"; ma=86400) or a DNS HTTPS record, then try QUIC on later connections, falling back to TCP if UDP is blocked.
| HTTP/1.1 | HTTP/2 | HTTP/3 | |
|---|---|---|---|
| Transport | TCP | TCP | QUIC over UDP |
| Format | Text | Binary frames | Binary frames |
| Concurrency | One request per connection at a time; about 6 connections | Multiplexed streams on 1 connection | Multiplexed, independent streams |
| HOL blocking | HTTP and TCP level | TCP level only | Largely removed |
| Header compression | None | HPACK | QPACK |
| Encryption | Optional | Effectively required by browsers | Always (TLS 1.3 built in) |
| New connection setup with TLS | TCP + TLS: 2 to 3 RTT | Same as HTTP/1.1 | 1 RTT, 0-RTT on resume |
| Survives network change | No | No | Yes (connection IDs) |
Interview tip
A strong one-line summary: "HTTP/2 fixed head-of-line blocking at the HTTP layer with multiplexing; HTTP/3 fixed it at the transport layer by replacing TCP with QUIC, and also cut handshake round trips and added connection migration."
CORS
Browsers enforce the same-origin policy: a script loaded from one origin may not read responses from another origin. An origin is the combination of scheme + host + port. https://app.example.com and https://api.example.com are different origins; so are http://example.com and https://example.com, and https://example.com and https://example.com:8443.
Without this rule, any website you visit could use your logged-in cookies to read your bank's pages via JavaScript.
CORS (Cross-Origin Resource Sharing) is how a server opts in to letting other origins read its responses. Important: CORS is enforced by the browser. curl, Postman and server-to-server calls ignore it entirely.
Simple requests
For "simple" requests (GET, HEAD or POST with only basic headers and a form-like content type), the browser sends the request with an Origin header and checks the response:
GET /api/products HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Vary: Origin
If Access-Control-Allow-Origin matches the page's origin (or is *), the script may read the response. Otherwise the browser blocks access and logs a CORS error. Note that the request was still sent and processed by the server; CORS protects reading, not sending. That is why CSRF defences are still needed.
Preflight requests
For anything else, such as PUT, DELETE, Content-Type: application/json, or custom headers like Authorization, the browser first sends a preflight OPTIONS request asking permission:
OPTIONS /api/products/7 HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: authorization, content-type
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: authorization, content-type
Access-Control-Max-Age: 600
Only if the preflight succeeds does the browser send the real DELETE. Access-Control-Max-Age lets the browser cache the preflight result (600 seconds here) to avoid an extra round trip each time.
Credentials: to send cookies cross-origin, the client sets credentials: "include" in fetch, and the server must reply Access-Control-Allow-Credentials: true and name the exact origin. The wildcard * is not allowed with credentials.
Common mistake
"My API works in Postman but fails in the browser" is almost always CORS. The fix belongs on the server (send the right Access-Control-Allow-* headers and handle OPTIONS), not in the frontend. Reflecting any incoming Origin back with credentials allowed is a security hole: it lets every website read your users' data.
REST basics
REST (Representational State Transfer) is an architectural style for APIs built on HTTP's own concepts. In practice, "RESTful" APIs follow these conventions:
- Resources are nouns, identified by URLs:
/users,/users/42,/users/42/orders. - Methods are the verbs: GET reads, POST creates, PUT replaces, PATCH updates, DELETE removes.
- Status codes carry the outcome: 201 with
Locationafter create, 404 when missing, 409 on conflict. - Stateless: every request carries its own authentication and context.
- Representations are negotiated, usually JSON.
- Cacheable responses use HTTP caching headers.
| Action | Request | Success response |
|---|---|---|
| List orders | GET /users/42/orders?status=paid&limit=20 | 200 with array |
| Fetch one | GET /orders/9001 | 200, or 404 |
| Create | POST /orders with JSON body | 201 + Location: /orders/9002 |
| Replace | PUT /orders/9002 | 200 or 204 |
| Partial update | PATCH /orders/9002 | 200 |
| Delete | DELETE /orders/9002 | 204 |
Avoid verbs in paths (/getUser, /deleteOrder); the method already says what happens. Version APIs (/v1/... or a header) so you can change them without breaking clients. Paginate lists with limit and a cursor. The API design lesson goes deeper, including GraphQL and gRPC.
Real-time updates: polling, long polling, SSE and WebSockets
Plain HTTP is client-initiated: the server cannot speak until asked. For chat, notifications, live scores or dashboards, the server needs to push updates. Four techniques exist.
Short polling. The client asks "anything new?" every few seconds. Simple, but wasteful (most answers are "no") and laggy (up to one interval of delay).
Long polling. The client sends a request; the server holds it open until there is news (or a timeout, say 30 seconds), then responds. The client immediately sends the next request. Near-real-time, works everywhere, but each message costs a full request-response and connection churn.
Server-Sent Events (SSE). The client opens one HTTP request with Accept: text/event-stream; the server keeps the response open and writes events as lines of text:
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
id: 101
event: price
data: {"symbol":"ACME","price":412.5}
id: 102
event: price
data: {"symbol":"ACME","price":413.0}
Server-to-client only, text only, but plain HTTP (works with proxies, HTTP/2 multiplexing) and the browser's EventSource API reconnects automatically, sending Last-Event-ID so the server can resume. Many AI chat interfaces stream tokens this way.
WebSockets. A full-duplex, message-oriented channel (both sides can send at any time) that starts as an HTTP request and then upgrades:
GET /chat HTTP/1.1
Host: chat.example.com
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=
After the 101, the TCP connection carries WebSocket frames, not HTTP. The Sec-WebSocket-Accept value is derived from the client key (a SHA-1 hash of the key plus a fixed string, Base64-encoded) to prove the server really understands WebSockets. Use wss:// for the TLS-encrypted version.
| Short polling | Long polling | SSE | WebSocket | |
|---|---|---|---|---|
| Direction | Client pulls | Client pulls, server delays | Server to client | Both ways |
| Latency | Up to poll interval | Low | Low | Lowest |
| Overhead per message | Full request | Full request | A few bytes | A few bytes |
| Protocol | HTTP | HTTP | HTTP | Upgraded from HTTP |
| Binary data | Yes | Yes | No (text) | Yes |
| Auto-reconnect | N/A | Manual | Built into EventSource | Manual |
| Good for | Rare updates | Fallback, simple notifications | Feeds, notifications, streaming output | Chat, games, collaborative editing |
Long-lived connections (SSE, WebSockets) need care at scale: load balancers must allow long idle timeouts, each connection uses server memory, and deploys must drain connections gracefully.
Interview tip
If asked to choose, reason from the direction of data. Server-to-client only (notifications, live feed)? SSE is simpler and plays well with HTTP infrastructure. Frequent messages both ways (chat, multiplayer)? WebSocket. Very rare updates or restrictive networks? Polling.
Hands-on with curl
curl lets you see real HTTP. Try these against any site you own or public test sites.
# Show request and response headers, plus TLS and connection details
curl -v https://example.com/
# Response headers only (sends HEAD)
curl -I https://example.com/
# Follow redirects
curl -L http://example.com/
# Send JSON with POST
curl -X POST https://api.example.com/orders \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"item":"book","qty":2}'
# Conditional request with an ETag you saw earlier
curl -i -H 'If-None-Match: "a1b2c3"' https://example.com/app.css
# Force a protocol version
curl --http1.1 -I https://example.com/
curl --http2 -I https://example.com/
curl --http3 -I https://example.com/ # needs a curl built with HTTP/3
# Simulate a CORS preflight
curl -i -X OPTIONS https://api.example.com/orders \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: POST"
# Time each phase of the request
curl -o /dev/null -s -w \
'dns=%{time_namelookup} tcp=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' \
https://example.com/
Reading curl -v output: lines starting with * are connection info (DNS result, TCP connect, TLS version and certificate, ALPN choice such as h2), lines with > are what curl sent, and lines with < are what the server returned.
The last command is a favourite debugging trick. The numbers are cumulative seconds from the start, so tls - tcp is the TLS handshake time and ttfb - tls is roughly the server's processing time plus one round trip. The modern networking and tools lesson uses it in a full "site is slow" walkthrough.
Interview questions
Q1. What does it mean that HTTP is stateless, and how do websites keep you logged in?
Each request is independent; the server does not automatically remember earlier ones. Login state is carried explicitly on every request, usually as a session id in a cookie (looked up in a server-side store) or a signed token such as a JWT in a cookie or Authorization header.
Q2. Which HTTP methods are idempotent? Is POST idempotent?
GET, HEAD, OPTIONS, PUT and DELETE are idempotent: repeating them leaves the server in the same state. POST is not, since repeating it may create duplicates, and PATCH is not guaranteed. To make POST retries safe, APIs accept an idempotency key and store the result per key.
Q3. What is the difference between PUT and PATCH?
PUT replaces the whole resource with the representation in the body and is idempotent. PATCH applies partial changes and is idempotent only if the changes are, such as setting a field rather than incrementing it.
Q4. Explain 401 versus 403, and 502 versus 504.
401 means the client is not authenticated; 403 means it is authenticated but not permitted. 502 means a proxy received an invalid response from the upstream server, for example a crashed app or a closed connection; 504 means the proxy gave up waiting for the upstream server.
Q5. How does HTTP caching work?
Freshness headers (Cache-Control: max-age) let caches reuse a response without contacting the server. Once stale, the cache revalidates with If-None-Match (ETag) or If-Modified-Since, and the server answers 304 Not Modified if unchanged, saving the body transfer. private limits caching to the browser, no-store forbids it, and no-cache forces revalidation each time.
Q6. What is the difference between no-cache and no-store?
no-cache allows storing but requires revalidation with the server before each use. no-store forbids storing the response anywhere, appropriate for sensitive data.
Q7. What do the Secure, HttpOnly and SameSite cookie attributes do?
Secure sends the cookie only over HTTPS. HttpOnly hides it from JavaScript, limiting session theft through XSS. SameSite controls whether it is sent on cross-site requests (Strict, Lax, None), which is the main defence against CSRF.
Q8. Sessions or JWTs, which would you use?
Server-side sessions are simple and instantly revocable but need a shared store. JWTs allow stateless verification across services but are hard to revoke, so they are kept short-lived with refresh tokens. For a typical browser app, a session cookie is often simpler and safer; for service-to-service or many independent APIs, signed tokens fit better.
Q9. What problems does HTTP/2 solve?
It multiplexes many concurrent streams over one TCP connection, removing HTTP-level head-of-line blocking and the need for six connections and domain sharding. It compresses headers with HPACK and uses binary framing. It does not solve TCP-level head-of-line blocking.
Q10. Why does HTTP/3 use UDP?
HTTP/3 runs on QUIC, which needs per-stream loss recovery, a combined transport and TLS handshake and connection migration, none of which TCP offers. Deploying a new transport protocol directly over IP would be blocked by middleboxes, so QUIC is built on UDP, which they already allow.
Q11. What is CORS, and why does a request work in curl but fail in the browser?
CORS is a browser mechanism that lets a server permit other origins to read its responses via Access-Control-Allow-* headers, relaxing the same-origin policy. curl does not enforce the same-origin policy at all, so the same request succeeds there. The fix is to configure the server's CORS headers and handle preflight OPTIONS requests.
Q12. What triggers a CORS preflight?
Any request that is not "simple": methods other than GET, HEAD and POST, non-form content types such as application/json, or custom headers such as Authorization. The browser sends an OPTIONS request first and proceeds only if the server allows the method, headers and origin.
Q13. Compare WebSockets, SSE and long polling.
Long polling holds a request open until data is ready, then the client reconnects, which works everywhere but has per-message overhead. SSE is a long-lived HTTP response that streams text events from server to client with automatic reconnection. WebSockets upgrade an HTTP connection into a full-duplex channel for low-latency messages in both directions.
Q14. What is head-of-line blocking?
When one delayed item holds up everything queued behind it. In HTTP/1.1 a slow response blocks later responses on the same connection; in HTTP/2 a lost TCP packet blocks all streams; HTTP/3 limits the stall to the affected stream.
Q15. What is the difference between 301, 302, 307 and 308?
301 and 308 are permanent redirects; 302 and 307 are temporary. 307 and 308 guarantee the client repeats the same method and body, while clients historically changed POST to GET for 301 and 302.
Key takeaways
- HTTP is a stateless request-response protocol: request line, headers, blank line, optional body; response has a status line instead.
- Safe methods do not change state; idempotent methods can be retried safely. POST is neither, PUT and DELETE are idempotent.
- Status classes: 2xx success, 3xx redirect or cache, 4xx client error, 5xx server error. Know 401/403 and 502/503/504 precisely.
- Caching = freshness (
Cache-Control) plus validation (ETag,Last-Modified, 304).no-cacheis notno-store. - Cookies get
Secure,HttpOnlyandSameSite; sessions are revocable, JWTs are stateless but hard to revoke. - HTTP/2 multiplexes over one TCP connection with HPACK; server push is deprecated. HTTP/3 uses QUIC over UDP to remove transport HOL blocking and speed up handshakes.
- CORS is a browser-enforced opt-in by the server; preflights use OPTIONS.
- Pick polling, long polling, SSE or WebSockets based on the direction and frequency of messages.
Next lesson
Continue with TLS and network security.

