Overview
An HTTP status code is the three-digit number a server returns with every response to say what happened. The first digit is the class - 1xx informational, 2xx success, 3xx redirection, 4xx client error (the request was wrong), and 5xx server error (the server failed on a valid request).
Correct status codes are part of an API's contract. Clients, browsers, caches, load balancers, retry libraries, and monitoring all make decisions from them. Returning 200 with an error message in the body, or 500 for invalid input, breaks retries, alerting, and caching in subtle ways.
A courier writes one code on the receipt: delivered (2xx), moved to a new address (3xx), wrong address from sender (4xx), or our truck broke down (5xx). The sender knows immediately whether to fix the address or simply try again later.
When to use it
- Designing REST API responses and error handling.
- Deciding which failures a client should retry.
- Configuring health checks, alerts, and dashboards.
- Debugging proxies, CDNs, and load balancers.
Where it shows up in interviews
Recognize it when: design the responses of an API.
- Design a REST API for orders
- Design a payment API
Recognize it when: which failures are safe to retry?
- Design a resilient API client
- Design webhook delivery
Where it is used in real software
Browsers follow 301/302 redirects automatically, and CDNs cache 200 and 404 responses by default rules while never caching most 5xx responses.
AWS ALB and Nginx mark targets unhealthy based on health-check status codes and return 502/503/504 when upstreams fail.
GitHub and Stripe return 401, 403, 404, 409, 422, and 429 with machine-readable error bodies so SDKs can react correctly.
Key terms
- 2xx success
- 200 OK, 201 Created, 202 Accepted, 204 No Content.
- 3xx redirection
- 301 permanent, 302/307 temporary, 304 Not Modified (use cache).
- 4xx client error
- 400 bad request, 401 unauthenticated, 403 forbidden, 404 not found, 409 conflict, 422 validation, 429 rate limited.
- 5xx server error
- 500 internal error, 502 bad gateway, 503 unavailable, 504 gateway timeout.
- Retry-After
- Header telling clients when to retry (with 429 or 503).
How it works, step by step
- 1Decide who is at fault
Client mistake is 4xx; server or dependency failure is 5xx.
- 2Pick the most specific code
404 for a missing resource, 409 for a state conflict, 422 for invalid fields.
- 3Return a structured body
Error code, message, and details (for example RFC 9457 Problem Details).
- 4Add helpful headers
Location for 201/3xx, Retry-After for 429/503, ETag for caching.
- 5Monitor by class
Alert on 5xx rate; track 4xx trends for client or UX issues.
STEP 12xx means success. Use the response; 201 includes a Location header for the new resource.
Choosing codes for an orders API
POST /orders and GET /orders/{id}
| Situation | Status | Why |
|---|---|---|
| Order created | 201 Created | New resource with Location header |
| Missing required field | 422 Unprocessable Content | Request is well-formed but invalid |
| No or expired token | 401 Unauthorized | Authentication needed |
| User cannot view this order | 403 Forbidden | Authenticated but not allowed |
| Order not found | 404 Not Found | Resource does not exist |
| Duplicate idempotency key with different body | 409 Conflict | State conflict |
| Too many requests | 429 Too Many Requests | Rate limit with Retry-After |
| Database down | 503 Service Unavailable | Temporary server-side failure |
NOWSituation: Order created | Status: 201 Created | Why: New resource with Location header
Precise codes let clients decide automatically whether to fix, wait, or retry.
Implementation
class HttpError extends Error { constructor(readonly status: number, readonly code: string, message: string, readonly headers: Record<string, string> = {}) { super(message); }} app.post("/orders", async (req, res, next) => { try { if (!req.user) throw new HttpError(401, "UNAUTHENTICATED", "Sign in required"); const errors = validateOrder(req.body); if (errors.length) return res.status(422).json({ code: "VALIDATION_FAILED", errors }); const order = await orders.create(req.user.id, req.body); res.status(201).location(`/orders/${order.id}`).json(order); } catch (err) { next(err); }}); // One place maps errors to status codes and safe messagesapp.use((err: unknown, _req: Request, res: Response, _next: unknown) => { if (err instanceof HttpError) { res.set(err.headers).status(err.status).json({ code: err.code, message: err.message }); } else { console.error(err); res.status(500).json({ code: "INTERNAL", message: "Something went wrong" }); }}); // Client: retry only what is safe to retryconst retryable = (status: number) => status === 429 || status === 502 || status === 503 || status === 504;Complexity and performance
The first digit carries most of the meaning.
200, 201, 204, 301, 302, 304, 400, 401, 403, 404, 409, 422, 429, 500, 502, 503, 504.
Trade-offs
Very specific codes help clients but can leak information (403 vs 404 reveals a resource exists); some APIs return 404 for both on purpose.
Teams differ on using 400 for all bad input or 422 for semantic validation errors; pick one convention and document it.
Variants and related techniques
Standard JSON error body with type, title, status, detail, and instance.
gRPC uses its own set (OK, INVALID_ARGUMENT, NOT_FOUND, UNAVAILABLE) that map to HTTP codes at gateways.
Common mistakes
- 200 OK with an error in the body.
Fix: Monitoring, caches, and retries all misread it; return the real status.
- 500 for invalid input.
Fix: Client mistakes are 4xx; reserve 5xx for server failures so alerts stay meaningful.
- Confusing 401 and 403.
Fix: 401 means not authenticated (who are you?); 403 means authenticated but not allowed.
Interview questions
What is the difference between 401 and 403?
401 Unauthorized means the request lacks valid authentication, so the client should sign in or refresh its token. 403 Forbidden means the server knows who the caller is but they do not have permission; retrying with the same credentials will not help.
Which status codes should a client retry?
Usually 429 (after Retry-After), 502, 503, and 504, plus network errors, and only for idempotent requests or requests with idempotency keys. 4xx errors other than 429 indicate a problem with the request itself.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Assign status codes to 10 API scenarios | Easy | Picking specific codes. |
| Design an error contract for a public API | Medium | Codes, bodies, and headers. |