Crux Read real HTTP exchanges — status line, headers, and body together — and pick what the response actually says and the highest-leverage fix.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at senior altitude — in orbit
◷ 14 min
Status codes are diagnosed on the wire, in the raw exchange. Read each request/response pair — line, headers, body — and decide what it really says and what a senior would change first.
Goal
Practise the on-the-wire reading loop: spot when the status and the body disagree, when a header is missing, and when the code attributes fault to the wrong side — then name the fix.
The upstream timed out, yet the status line is 200. What is the consequence and the fix?
Heads-up Only clients that parse and check your bespoke envelope detect it. Caches, dashboards, and retry middleware never read the body; they branch on the 200 and treat a total outage as perfect health. The status must carry the truth.
Heads-up Hiding a 5xx behind 200 does not avoid alarm; it removes the signal that would have driven the correct automatic recovery. The error must surface in the status so the system reacts.
Heads-up Putting the number in the body changes nothing for the machines that branch on the HTTP status line. The status code itself must be a 5xx.
Exchange 2 — 401 vs 403
DELETE /tenants/acme/users/77 HTTP/1.1Authorization: Bearer eyJhbGc... # valid, non-expired token for user in tenant 'globex'HTTP/1.1 401 UnauthorizedWWW-Authenticate: Bearer{ "error": "you may not delete users in another tenant" }
Quiz
Completed
The token is valid; the caller simply lacks permission for another tenant. What is wrong with this response, and what should it be?
Heads-up 401 and 403 are both auth-related but mean different things. 401 = not authenticated (fix: log in / refresh). 403 = authenticated but not allowed (no credential change helps). The token is valid here, so it is 403.
Heads-up The request is well-formed; nothing about its syntax is wrong. The problem is permission, not structure. 400 would tell the client to fix the request shape, which is irrelevant.
Heads-up There is no resource-state conflict — the user exists and the request parsed. The caller is simply not permitted. That is 403, not 409.
Exchange 3 — 503 without Retry-After, and a hot retry loop
GET /catalog/items?page=3 HTTP/1.1HTTP/1.1 503 Service UnavailableContent-Type: application/json{ "error": "overloaded, shedding load" }# client behaviour: immediately re-issues the GET in a tight loop, no delay
Quiz
Completed
The server is shedding load with 503 but sent no Retry-After, and the client retries instantly. What are the two fixes — one per side?
Heads-up 503 is the one 5xx that should carry Retry-After precisely so clients back off intelligently. Omitting it leaves clients guessing — the server shares the blame for the storm.
Heads-up Even without Retry-After, hammering a known-overloaded server with zero delay is the textbook way to keep it overloaded. The client must back off with jitter regardless.
Heads-up GET is idempotent, so retrying is safe and correct on a 503 — the issue is the timing (instant loop) and the missing Retry-After, not whether to retry.
Exchange 4 — conditional update and 412
PUT /docs/42 HTTP/1.1If-Match: "v7"Content-Type: application/json{ "title": "Q3 plan", "body": "..." }HTTP/1.1 412 Precondition Failed# the stored ETag is now "v9" — someone else saved twice since the client last read
Quiz
Completed
The conditional PUT returns 412 because the ETag moved from v7 to v9. What does this tell the client, and what should it do?
Heads-up 412 is about the precondition header (If-Match) evaluating false, not the body. The body parsed fine. The fix is to refetch and update the precondition, not to repair JSON.
Heads-up 412 says the precondition did not hold (the ETag moved), which means the resource still exists but changed. A deletion would surface differently. Refetch to see the current state.
Heads-up Dropping If-Match defeats the optimistic-concurrency protection and silently overwrites someone else's v8/v9 changes — the exact lost-update bug the precondition prevents. Reconcile against the new version instead.
Recap
On the wire, read the status line, headers, and body as one unit. A 200 with an error body lies to every machine that branches on status — return the real 5xx. 401 vs 403 turns on authentication vs authorization, and getting it wrong loops the client. A 503 should carry Retry-After, and a client without one must back off with jitter rather than hammer. A 412 is optimistic concurrency working — refetch, reconcile against the new ETag, and retry the conditional write rather than forcing the overwrite.