Core CS · Computer Networks
Ask once, get answered once, be forgotten
HTTP is one request, one reply, and no memory in between. Cookies are how you are recognised on the next request. Caching is how the next request never happens. HTTPS is the same conversation, sealed inside TLS.
Run three requests and watch the cookie and the cache appear →01 The idea
The server answers, and then forgets you completely
HTTP, the HyperText Transfer Protocol, is the application-layer protocol the web is built on. Up to HTTP/2 it runs on top of TCP, on port 80 in the clear and port 443 inside TLS, and HTTP/3 carries the identical messages over QUIC on UDP instead, which section 03 gets to. Whichever transport is underneath, the whole of it is one pattern repeated: the client sends a request, the server sends back exactly one response, and the exchange is over. There is no notion of a session in the protocol itself. There is no login state, no shopping cart and no “where you were”. There is a message and a reply.
That property has a name you will be asked to define. HTTP is stateless: the server retains nothing about a client between two requests. It is easy to hear this as a shortcoming, and it is not. It is a deliberate design decision and it is the reason the web scales at all. If a server kept per-client state in memory, every request from you would have to come back to the same machine. Because it keeps none, any of a thousand machines behind a load balancer can serve any request, a crashed server loses nothing that matters, and a new server can join the pool and start answering immediately.
The bill for that choice lands on the client. If the server remembers nothing, then every request must carry everything needed to answer it. Which page you want, which host you meant, what content types you accept, and, crucially, who you are. That last one is what cookies exist for. A cookie is a small named value the server hands the browser in a Set-Cookie response header, and the browser hands back in a Cookie request header on every later request to that host. The server has still forgotten you. It recognises you only because you re-introduced yourself, unprompted, in the message.
The second consequence is about cost. Every request is a fresh round trip over TCP, and a round trip across a continent is tens of milliseconds you cannot argue with. A page is not one request, it is dozens. So the single largest performance lever in HTTP is not making requests faster, it is not making them. That is what the caching headers are for: the server labels a response with how long it stays usable and with a version tag, and the browser uses those two facts to skip the network entirely, or, when it cannot skip it, to ask a question so small that the answer fits in four lines with no body attached.
So this lesson has three layers stacked on one protocol, and they are stacked in that order for a reason. The message format is the protocol. Cookies are state layered on top of a protocol that has none. Caching is the avoidance of the protocol altogether. HTTPS then wraps the entire stack in TLS without changing a single thing about it: the same methods, the same headers, the same status codes, carried inside an encrypted record on port 443.
02 Worked example
One GET and its 200, read header by header
One small exchange, and it is the same exchange for the rest of the lesson: the console in section 04 replays it three times, and every byte count in the lesson is measured on it. A browser wants https://examate.in/pricing.html, a 1,200-byte page it has never seen before. The scheme is https, and that is not a detail you can drop. The message text below is byte for byte what plain HTTP would look like. But the cookie in step 4 is marked Secure, and a browser will not store a Secure cookie handed to it over plain HTTP, nor send one back over one. Read left to right.
Written out, the request is five lines and the response is seven, and both end with an empty line you cannot see when you print them. Here is the request exactly as it goes on the wire, with each line terminated by a carriage return and a line feed:
GET /pricing.html HTTP/1.1Host: examate.inUser-Agent: Mozilla/5.0Accept: text/htmlConnection: keep-alive <-- the blank line that ends the headers
Check the arithmetic, because the console recomputes it and it should agree with you. The five text lines are 26, 16, 23, 17 and 22 characters, which is 104. Each line is terminated by CRLF, two bytes, so add 10 to get 114. The final blank line is another CRLF, so the request is 116 bytes. Nothing in it is data. All of it is metadata, and that is worth holding on to, because it is exactly why HTTP/2 bothered to compress headers.
Now the reply, and the highlighted node is where the rest of the lesson comes from:
HTTP/1.1 200 OKDate: Mon, 06 Apr 2026 10:00:00 GMTContent-Type: text/html; charset=utf-8Content-Length: 1200Cache-Control: max-age=60ETag: "v7-9f2a"Set-Cookie: sid=8f31c2; Path=/; HttpOnly; Secure; SameSite=Lax <-- blank line, then 1200 bytes of HTML
The status line is HTTP/1.1 200 OK: version, then a three-digit code, then a human-readable reason phrase that no program should ever parse. Content-Type tells the browser to treat the bytes as HTML rather than offer them as a download, and the charset parameter is what stops the page rendering as mojibake. Content-Length tells the receiver exactly where this body ends, which matters because the connection is being kept open for the next request and something has to mark the boundary.
Then the three that do the work. Cache-Control: max-age=60 is a promise about time: this copy may be reused for 60 seconds from the moment it was stored, with no request to anyone. ETag: "v7-9f2a" is a promise about identity: this exact body has that tag, and if the tag ever changes the body has changed. Set-Cookie is a promise about nothing at all; it is an instruction. Store sid=8f31c2 and send it back to me every time.
Those three headers are three different answers to three different questions, and mixing them up is the classic confusion. Do I need to ask? is answered by max-age. Has it changed? is answered by the ETag. Who is asking? is answered by the cookie. The first saves a whole round trip, the second saves a whole body, and the third makes a stateless protocol behave as though it knew you.
One last thing about that Set-Cookie line, because it is the densest 62 bytes in the lesson. sid=8f31c2 is the name and value, and the value is deliberately meaningless: it is a lookup key into a session record the server keeps in its own database, not the session itself. Path=/ scopes it to the whole site. HttpOnly hides it from JavaScript. Secure means it will never be sent over plain HTTP. SameSite=Lax means it will not be attached to requests that another site caused your browser to make. Section 03 puts a specific attack next to each of those three, and being able to name the attack rather than the attribute is the whole of that question.
03 Mechanics
The methods, the codes, the cookies, the cache and the versions
Five tables, in the order the questions arrive, and a sixth on what HTTPS does and does not hide. Start with the methods, because the two properties in columns two and three are the whole of the PUT versus POST question. Safe means the request does not change server state, so a crawler may fire it at will. Idempotent means doing it twice has the same effect on the server as doing it once, which is what makes an automatic retry after a lost response harmless. Every safe method is idempotent; the reverse is not true, and PUT and DELETE are the two rows that prove it, because both change server state and both are still safe to retry.
| Method | Safe | Idempotent | Request body | What it means |
|---|---|---|---|---|
| GET | yes | yes | no | Fetch the representation at this URL and change nothing. The default for every link and every address you type. |
| HEAD | yes | yes | no | Identical to GET, but the server sends the headers only and no body. Used to check size, type or freshness without paying for the download. |
| OPTIONS | yes | yes | no | Ask what the server permits on this URL. The browser sends one automatically as the preflight before certain cross-origin requests. |
| PUT | no | yes | yes | Replace whatever is at this exact URL with this body. The client chooses the URL, which is what makes it idempotent: the same PUT twice leaves the same single resource in the same state. |
| DELETE | no | yes | no | Remove the resource at this URL. Idempotent because after the first one it is gone, and asking again for it to be gone changes nothing. The second call may answer 404, but the state is the same. |
| POST | no | no | yes | Submit this data to the resource at this URL and let the server decide what to do with it. Two identical POSTs to /orders create two orders, which is precisely why it is not idempotent. |
| PATCH | no | no | yes | Apply a partial change rather than a full replacement. Not idempotent in general, because an instruction like add 10 to the balance applied twice is not the same as applied once. |
Why PUT versus POST is the standard question. The distinction is not update against create; both can create. It is who names the resource. PUT /users/42 says here is the complete state of user 42, put it there, and repeating it is harmless. POST /users says here is a new user, you pick the address, and repeating it makes a second user. That is why a browser warns you before re-submitting a form and never warns you before reloading a page. It is also why a mobile client on a bad network can retry a PUT automatically but must not retry a POST without help.
Now the status codes. The first digit is the class and it is the part you should never get wrong, because it says whose fault it is. The specific codes below are the ones that come up by name.
| Class | Code | Name | What it actually means |
|---|---|---|---|
| 1xx informational | 100 | Continue | Carry on and send the body you announced. Rare in browsers; you meet it with large uploads. |
| 2xx success | 200 | OK | It worked, and the body is the answer. |
201 | Created | A POST or PUT made something new. The Location header names where it now lives. | |
204 | No Content | It worked and there is deliberately nothing to send back. Common for a successful DELETE. | |
| 3xx redirection | 301 | Moved Permanently | The URL has changed for good. Browsers and search engines update their records, and the browser may cache the redirect for a long time. |
302 | Found | Temporary. Go to the Location this once, but keep treating the original URL as the real one. | |
304 | Not Modified | Your cached copy is still correct. Status line and headers, no body. This is a success, not a failure. | |
| 4xx client error | 400 | Bad Request | The message itself is malformed and the server could not make sense of it. Nothing to do with permissions. |
401 | Unauthorized | We do not know who you are. Authenticate and try again. The name is a historical misnomer; it means unauthenticated. | |
403 | Forbidden | We know exactly who you are, and you still may not have this. Signing in again will not help. | |
404 | Not Found | There is no resource at this URL. Also used deliberately to hide the existence of one. | |
| 5xx server error | 500 | Internal Server Error | The server broke while handling a request that was perfectly valid. The generic crash. |
502 | Bad Gateway | This server is acting as a proxy, and the server behind it gave an invalid response or none at all. | |
503 | Service Unavailable | This server is up and answering, but cannot serve right now: overloaded or in maintenance. May carry Retry-After. |
The two pairs that get swapped. 401 against 403 is about identity: 401 means who are you, 403 means we know, and no. If a signed-in student requests the admin dashboard, that is 403; sending 401 tells the browser to prompt for credentials that will not help. 502 against 503 is about which machine is broken: 502 comes from a proxy whose upstream failed, so the thing you are talking to is fine and the thing behind it is not; 503 comes from a server that is talking to you perfectly well and telling you it cannot do the work. In practice a 502 from nginx almost always means the application process died or was never started, which is why it is the first thing you check after a deploy.
Cookies next. There is nothing to a cookie but a name, a value and a list of attributes, and the attributes are the entire security surface. The right-hand column is the one to memorise, because the question is never name the attributes, it is what does each one stop.
| Written as | What it does | What it stops |
|---|---|---|
| no Expires and no Max-Age | Session cookie. The browser holds it in memory and deletes it when the browsing session ends. | Not a defence in itself. It limits how long a stolen value stays useful, and it is the right default for a login. |
| Max-Age=1209600 or Expires=… | Persistent cookie. Written to disk and survives a browser restart until that moment. 1209600 seconds is 14 days. | Nothing. This is a convenience feature, and it widens the window in which a stolen cookie still works. |
| Secure | The browser sends it only over HTTPS, never over a plain HTTP request. | An eavesdropper on shared Wi-Fi reading the session id straight out of a plaintext request and replaying it. |
| HttpOnly | document.cookie cannot see it, so no script running on the page can read it. | Session theft through cross-site scripting. An injected script can still act as you, but it cannot walk off with the cookie. |
| SameSite=Lax | Not attached to cross-site requests, except top-level navigations that use GET. This is the modern browser default. | Cross-site request forgery. Another site cannot make your browser perform a state-changing action as you. |
| SameSite=Strict | Never attached when the request originated on another site at all, including an ordinary link. | The same, more tightly, at the cost of a user arriving from an external link and appearing to be signed out. |
| Domain and Path | Limit which hosts and which URL prefixes the cookie is attached to. | Handing your session cookie to more of your own infrastructure, and more subdomains, than actually needs it. |
The cookie is a key, not the state. Almost every real session cookie holds an opaque random identifier and nothing else, and the actual session data sits in the server’s store keyed by it. That matters for two reasons. A cookie is sent on every single request to that host, so a fat cookie is a tax on every page load. And anything you put in a cookie, the user can read and edit, so a cookie that says role=admin is not a session, it is a vulnerability. If state must live in the cookie, it has to be signed, which is what a JSON Web Token is doing.
Caching. Two mechanisms live here and they are commonly confused, so separate them before you read the table. Freshness decides whether a request is made at all, and it is driven by max-age. Validation decides whether a body is sent, and it is driven by the ETag and the conditional request. The first saves a round trip; the second saves a payload.
| Header, and which message it rides on | What it means |
|---|---|
| Cache-Control: max-age=60 · response | Reusable for 60 seconds from the moment it was stored. Inside that window the browser makes no request at all, so the server never learns the page was viewed. |
| Cache-Control: no-cache · response | Store it, but check with the origin before every single reuse. The name is misleading: it does not mean do not cache, it means do not use without asking. |
| Cache-Control: no-store · response | Do not write it down anywhere, not to disk and not to memory. This is the one for a bank statement or a password reset page. |
| Cache-Control: private · response | The browser may store it. A shared cache, meaning a proxy or a CDN, may not. Use it on anything personalised. |
| ETag: "v7-9f2a" · response | An opaque version tag for this exact representation. The client never interprets it; it stores it and quotes it back. |
| If-None-Match: "v7-9f2a" · request | Send me the body only if the tag is no longer current. This turns a download into a question. |
| Last-Modified / If-Modified-Since | The older, date-based validator. Its resolution is one second, so two edits inside the same second are indistinguishable. Prefer the ETag when both are available. |
| 304 Not Modified · response | Status line and headers, no body. Your copy is still correct, keep using it, and the freshness window starts again. |
| Vary: Accept-Encoding · response | Warns a cache that this one URL has more than one correct answer, keyed on the request headers named here. Without it a cache can serve a gzipped body to a client that cannot decode it. |
Caches live in three places, and only one of them is yours. The browser cache sits on the user’s own disk and serves exactly one person. A forward or reverse proxy sits in the middle. A CDN is a shared cache with edge nodes placed near users all over the world. The browser cache and the CDN behave identically as far as the headers are concerned, and that is a trap: a cache keys its entries on the URL, not on who asked, so a personalised page that reaches a shared cache without private can be handed to the wrong person. Section 04 shows only the browser cache, because that is the one every request passes through first.
The versions, briefly, because the question is usually what problem did each one solve rather than the specification.
| Version | Runs over | What it introduced | What still hurts |
|---|---|---|---|
| HTTP/1.0 | TCP | The status line, the header block and the method vocabulary that everything since has kept. | one request per connection — a new TCP handshake for every image on the page |
| HTTP/1.1 | TCP | Persistent connections by default, so one connection carries many requests. The mandatory Host header, which is what allows many sites on one IP address. Chunked transfer encoding for bodies of unknown length. | head of line blocking — responses come back in the order the requests went out, so one slow response stalls everything queued behind it. Browsers worked around it by opening about six connections per host. |
| HTTP/2 | TCP, in practice always inside TLS | A binary framing layer instead of text. Many independent streams multiplexed over a single connection, so responses may come back interleaved and in any order. HPACK header compression, which matters because those 116 bytes of pure metadata repeat on every request. | TCP-level head of line blocking — TCP still delivers bytes in order, so one lost segment stalls every stream sharing that connection |
| HTTP/3 | QUIC, which runs on UDP | Loss recovery per stream, so a lost packet stalls only its own stream. QPACK header compression. TLS 1.3 folded into the transport handshake, so a connection can be established in one round trip. | UDP is blocked or rate limited on some corporate and campus networks, so clients keep an HTTP/2 fallback ready. |
Read that table as one problem being chased down three layers. HTTP/1.1 removed the per-request connection but left ordering at the HTTP layer. HTTP/2 removed the ordering at the HTTP layer but inherited it from TCP underneath. HTTP/3 could only finish the job by leaving TCP, which is why it is built on UDP: not because UDP is faster, but because QUIC needed to implement its own ordering and could only do that on a transport that imposed none. Saying “HTTP/2 solved head of line blocking” without the TCP clause is the answer that gets marked down.
Finally HTTPS, stated precisely, because the loose version of this answer is very easy to catch. HTTPS is not a different protocol. It is the identical HTTP you have been reading, with identical methods, headers and status codes, carried as application data inside a TLS session, on port 443 instead of port 80. TCP connects first, TLS negotiates on top of it, and only then does the request line travel, encrypted. So it is fair to be exact about what that buys and what it does not.
| Part of the exchange | Hidden by TLS |
|---|---|
| The method, the path and the whole query string | yes — all of it is inside the encrypted record, so a URL containing a token is protected on the wire, though not in the server’s logs |
| Every header on both messages, including Cookie and Set-Cookie, and the request body | yes |
| The status code and the response body | yes |
| The destination IP address and port 443 | no — they sit in the IP and TCP headers, which have to be readable for the packet to be routed at all |
| The hostname in the TLS ClientHello, the SNI extension | no — it is sent before encryption begins, because the server needs it to choose which certificate to present. Encrypted Client Hello is the fix and is not yet universal. |
| The size and timing of what you transfer | no — enough on its own to guess which page of a known site you loaded |
| The DNS lookup that found the address in the first place | no — plaintext UDP on port 53 unless DNS over HTTPS or DNS over TLS is in use |
The handshake, in the right order, with the round trips right. In TLS 1.2 the client sends ClientHello; the server answers with ServerHello, its Certificate and ServerHelloDone; the client sends ClientKeyExchange, ChangeCipherSpec and Finished; the server answers ChangeCipherSpec and Finished. That is two round trips before a single HTTP byte moves, on top of TCP’s one, and the certificate travels in the clear. TLS 1.3 collapses it: the client guesses the key exchange group and puts its key share in the ClientHello, the server replies with its own share in the ServerHello, and everything after the ServerHello is already encrypted, including the certificate. That is one round trip. Only the two Hello messages are plaintext in TLS 1.3. If you remember one thing here, remember that 1.3 is one round trip and 1.2 is two, and never say it the other way round.
What TLS actually gives you, in three words. Confidentiality, nobody in the middle can read it. Integrity, nobody in the middle can change it without being detected. Authentication, the certificate presented by the server was signed by a certificate authority your machine already trusts, and it binds that hostname to that public key. The third one is the part people leave out, and it is the one that stops an attacker from pretending to be the site. Encryption without authentication would be encryption with whoever intercepted you.
05 Cheat sheet
Fifteen answers you should be able to give in ten seconds
Every row is something an interviewer asks flat out. The right-hand column is the specific wrong answer that gets given, not a general caution.
| What they ask | The answer | The trap |
|---|---|---|
| The parts of an HTTP message | start line · headers · blank line · optional body | Leaving out the blank line. It is the only thing that ends the header block. |
| The header HTTP/1.1 made mandatory | Host | Answering User-Agent. Host is what lets many sites share one IP address. |
| Safe and idempotent, defined | safe = changes no server state · idempotent = twice equals once | treating them as one property — DELETE is idempotent and not safe |
| Which methods are idempotent | GET, HEAD, OPTIONS, PUT, DELETE | including POST or PATCH — neither is |
| PUT or POST | PUT: the client names the URL · POST: the server decides | Saying “PUT updates, POST creates”. Both can create; the difference is who chooses the address. |
| 401 against 403 | 401 = we do not know you · 403 = we know you and no | 401 for a signed-in user without permission — that is 403 |
| 301 against 302 | 301 permanent, caches and search engines update · 302 temporary | Sending 301 during a migration you may reverse. Browsers cache it and users never come back. |
| 502 against 503 | 502 = my upstream failed · 503 = I am up but cannot serve | Reading a 502 as “the site is down”. The proxy answered you perfectly. |
| What a 304 carries | status line and headers, no body | reading 304 as a failure — it is a success meaning your copy is still right |
| Turning a stored copy into a question | If-None-Match, carrying the stored ETag | Reaching for If-Modified-Since when an ETag exists. The date has one-second resolution. |
| no-cache against no-store | no-cache stores and revalidates · no-store never writes it down | no-cache on a bank statement — it is still on disk |
| The three cookie attributes | Secure = HTTPS only · HttpOnly = hidden from JS · SameSite = not cross-site | Naming the attributes without naming the attack each one stops. |
| What HTTP/2 actually fixed | multiplexed streams on one connection, HPACK header compression | “it removed head of line blocking” — TCP still has it; HTTP/3 needed QUIC |
| HTTPS, precisely | the same HTTP inside a TLS session, port 443 | “it encrypts everything” — the IP, the port and the SNI hostname are visible |
| TLS 1.2 against TLS 1.3 setup | 1.2 needs two round trips · 1.3 needs one | Saying it the other way round, or forgetting TCP’s own round trip underneath both. |
06 Where & why
These headers are printed by tools you will run this week
Nothing in this lesson is a teaching abstraction. Every header named above is a literal string that real software emits and real tools display, and three of the four systems below differ from the textbook in a way worth naming out loud.
Every response through Cloudflare carries cf-cache-status, and the values are the vocabulary of section 03: HIT served from the edge, MISS fetched from your origin, EXPIRED stale so it revalidated, REVALIDATED the origin answered 304, BYPASS your headers forbade caching. It honours Cache-Control: private and no-store by refusing to store, which is the whole reason those directives exist. TLS also terminates on the edge machine, so the certificate the browser validates is Cloudflare’s, not yours.
For a static file nginx builds a strong ETag automatically from the file’s last-modified time and its size, so If-None-Match and 304 work with no configuration at all. expires 1y; emits both Cache-Control: max-age=31536000 and a legacy Expires date. And when nginx is proxying to an application it is nginx, not your code, that answers 502 Bad Gateway when the upstream refuses the connection and 504 Gateway Timeout when it accepts but never replies. Reading which of the two you got tells you whether the process is dead or merely stuck.
The Size column reads (disk cache) or (memory cache) for a request that never left the machine, which is step 3 of the console with nothing on the wire. A row showing 304 has a Size of a few hundred bytes against a Content column of the full page, and that gap is the body you did not download. Ticking Disable cache makes the browser send Cache-Control: no-cache on every request, which is why a page behaves differently with DevTools open. The Application panel lists the cookie jar with separate columns for HttpOnly, Secure and SameSite.
curl -v https://examate.in/pricing.html prints the request line and every request header after > and the response after <, so the two panels in section 04 are literally what you see. curl -I sends a HEAD rather than a GET, which is how you read Cache-Control and ETag without pulling the body. Add -H 'If-None-Match: "v7-9f2a"' and you have made the conditional request by hand and can watch the 304 come back.
07 Interview questions
The order these questions actually arrive in
This is the most asked topic in the application layer, and it is asked in layers. You describe the message, they ask what stateless means, then how login works anyway, then which method to use, then the codes, then caching, then HTTPS, then the versions. Give the header name every time you can. An answer with real header names in it reads as experience; one without reads as a memorised definition.
What actually travels over the wire when I open a page? Describe the HTTP message.
HTTP is stateless. So how does a site remember that I am signed in?
What do safe and idempotent mean, and which methods are which?
PUT or POST to create something? How do you decide?
401 or 403? Which one, and why does it matter?
301 or 302? Does it matter which one you send?
502 or 503? What does each one tell you about where to look?
A browser already has a copy of the page. How does it avoid downloading it again?
no-cache and no-store sound like the same thing. What is the difference?
Is HTTPS a different protocol from HTTP?
On HTTPS, what is actually hidden and what is not?
HTTP/1.1, HTTP/2, HTTP/3. What problem does each one solve?
08 Practice problems
Six to work through with a pen
Write out the header lines before you answer any of these. Three of the six turn on caching, one turns on what a cookie attribute is actually defending against, one turns on a property a method does not have, and the last cannot be answered without deciding what a filename is allowed to promise.