Quick Mental Map: The Five Categories
HTTP status codes are organized into five families by their first digit. If you remember nothing else from this page, remember this:
| Range | Meaning | Mental model |
|---|---|---|
| 1xx | Informational | "Got it — standby." Continue processing. |
| 2xx | Success | "Done." The request worked. |
| 3xx | Redirection | "Look somewhere else." Client needs to take another action. |
| 4xx | Client Error | "You sent something wrong." Client's fault. |
| 5xx | Server Error | "I broke." Server's fault. |
The 4xx/5xx split is the most common source of debate — when a request hits a downstream service that's broken, is that a 4xx or 5xx for your API? Convention: if the client could have done anything different to avoid the failure, it's a 4xx. If the failure is something only your infrastructure could fix, it's a 5xx.
The Status Code Distinctions That Matter Most in Production
Most engineers learn the high-traffic codes (200, 404, 500) on day one and never go deeper. But a handful of distinctions show up in real bug reports and incident postmortems over and over:
- 401 vs 403 — auth not provided vs auth provided but insufficient.
- 400 vs 422 — malformed request vs semantically invalid request.
- 404 vs 410 — might-not-exist vs definitely-removed.
- 429 vs 503 — deliberate per-client rate limit vs server-side overload.
- 502 vs 504 — bad upstream response vs upstream timeout.
- 301 vs 308 — permanent redirect (may change method to GET) vs permanent redirect (must preserve method).
Getting these right matters more for APIs than for human-facing pages, because client libraries (axios, fetch, Stripe SDK, retry middleware) make automatic decisions based on the code you return. A 429 with a Retry-After header gets retried correctly. A 503 returned for rate-limiting often gets retried too aggressively and amplifies your overload.
How REST Frameworks Map Internal Errors to HTTP Codes
If you're building an API, here's how the most-used modern frameworks default:
- FastAPI / Pydantic — validation errors return 422 Unprocessable Entity by default. Authorization failures via
Depends()return 401 or 403 depending on the dependency type. - Rails 7+ — strong parameter errors return 400. Model validation errors return 422 when using
render :unprocessable_entity. Authentication failures via Devise return 401. - Laravel — FormRequest validation failures return 422 with structured errors. Policy denials return 403. Missing models via route-model binding return 404.
- Next.js Route Handlers / Express — nothing automatic; you set the status yourself with
NextResponseorres.status(). Most teams adopt a small status-code helper. - tRPC / GraphQL — both typically return 200 at the HTTP layer regardless of business logic outcome, with the error inside the response body. This trips up logging and monitoring tools that bucket by HTTP status.
HTTP Status Codes in Job Postings (And What They Signal)
HTTP status code knowledge shows up in backend, platform, and SRE interview loops constantly — usually disguised as a system-design question. "How would you design rate limiting?" tests 429 and Retry-After. "How would you handle a flaky upstream?" tests 502 vs 504. "How would you build idempotent POSTs?" tests 200/201/409 patterns and the Idempotency-Key header.
If you're hiring or job-searching for these roles, our culture-first backend & platform jobs board is filterable by what the team is actually building. The engineering-driven companies in our directory tend to ask sharper API-design questions on their loops than less-eng-led companies do — a useful signal either way.