System DesignBeginner 12 min Lesson 2 of 42

Request and Response

What a request and a response actually contain, why statelessness matters, and how headers shape almost everything.

System Design · Lesson 2 of 42
0/42 done(0%)

What is it? #

A request is a piece of text with four parts: a method, a path, headers and an optional body. A response has a status code, headers and usually a body.

That is the whole protocol at the surface level. Everything else — authentication, caching, compression, content types — is expressed through headers.

HTTP is stateless. Each request arrives with no memory of the previous one. If the server needs to know who you are, the request must carry that information, usually in a cookie or an Authorization header.

Statelessness is what makes scaling possible. Any server can handle any request, because no server holds the conversation in memory.

Think of it like this #

Sending a letter. The envelope carries the address and the postmark (headers), the letter inside is the content (body), and the type of service — ordinary, registered, return receipt — is the method.

The postal system does not remember your last letter. If this one needs context, you write it in.

Simple example #

A browser requests a product page while logged in. The request carries a cookie identifying the session; the response carries the HTML, caching instructions and a content type.

Code #

TEXT
REQUEST

GET /api/products/42?currency=INR HTTP/1.1
Host: shop.example.com
Accept: application/json
Authorization: Bearer eyJhbGciOi...
User-Agent: Mozilla/5.0
Accept-Encoding: gzip

(no body for a GET)


RESPONSE

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 87
Cache-Control: public, max-age=300
ETag: "a1b2c3"

{"id": 42, "name": "Keyboard", "price": 2499, "currency": "INR"}
PYTHON
import requests

response = requests.post(
    "https://api.example.com/orders",
    json={"sku": "P-1", "qty": 2},              # becomes the body
    headers={
        "Authorization": "Bearer token123",     # who is asking
        "Idempotency-Key": "order-abc-123",     # safe to retry
    },
    timeout=10,
)

print(response.status_code)          # 201
print(response.headers["Content-Type"])
print(response.json())               # parsed body
print(response.elapsed.total_seconds())
TEXT
Headers worth knowing

Content-Type      what format the body is in
Accept            what formats the client can handle
Authorization     credentials
Cache-Control     how long this may be cached, and by whom
ETag              a version tag, so the client can ask "changed since this?"
Set-Cookie        server asks the client to store something and send it back
X-Request-ID      a trace identifier, useful across services
Retry-After       how long to wait before trying again

How it works #

The first line of a request carries the method and path. The query string after ? passes parameters for filtering or options.

Headers are key-value metadata. Accept says what the client wants back; Content-Type on a response says what it actually got. Mismatches here cause a large share of integration bugs.

The body carries data. GET requests normally have none; POST and PUT usually do.

The response status code is the summary: 2xx worked, 3xx redirect, 4xx the client made a mistake, 5xx the server did.

Cache-Control: public, max-age=300 tells browsers and proxies they may reuse this response for five minutes. That single header can remove most of your traffic — or, set wrongly, serve stale prices for a day.

ETag enables conditional requests. The client sends the tag back next time, and the server can answer 304 Not Modified with no body at all.

The Idempotency-Key in the Python example is the practical answer to statelessness plus retries: if the same key arrives twice, the server should return the original result rather than creating a second order.

Real-world use #

Debugging web problems is mostly reading requests and responses. Browser developer tools and curl -v show you exactly what was sent and received, which resolves most "it works locally" mysteries.

Caching headers are one of the highest-value settings in web performance. Static assets get long cache lifetimes with versioned filenames; dynamic pages get short ones or none.

Statelessness explains why session data lives in a cookie, a token, or a shared store like Redis rather than in server memory. Keeping it in memory breaks the moment a second server appears.

Request IDs propagated through headers are what make distributed tracing possible: one identifier follows a request through every service, so logs can be stitched together.

Common mistakes #

  • Storing session state in server memory, which breaks with more than one server.
  • Setting long cache lifetimes on responses that contain user-specific data.
  • Ignoring the status code and parsing the body regardless.
  • Sending JSON without a Content-Type: application/json header.
  • Retrying non-idempotent requests without an idempotency key.

Practice #

Use curl -v against a public API and identify the method, path, three request headers and three response headers. Then send the same request with Accept: text/html and compare. Finally, make a request twice and check whether any caching headers changed the second response.

Quick quiz

  1. 1. What does "HTTP is stateless" mean?

  2. 2. What does `Cache-Control: max-age=300` mean?

  3. 3. What is an ETag used for?

  4. 4. Why send an Idempotency-Key?

  5. 5. Where should session state live in a multi-server setup?

Summary

  • A request has a method, path, headers and optional body; a response has a status, headers and body.
  • Headers carry authentication, content types, caching rules and tracing.
  • HTTP is stateless, so context travels with every request.
  • Cache headers are among the highest-impact settings in web performance.
  • Idempotency keys make retries safe for actions that change data.