The fastest way to lose points in an API design round isn’t a shaky database schema. It’s returning 200 OK for a request that failed, or claiming PATCH is idempotent when it isn’t. Interviewers reach for HTTP because it’s a cheap proxy: the status code you pick and the method you choose tell them how carefully you reason about retries and failure.
The rest is grouped the way the questions come up, with enough reasoning behind each code to survive a follow-up.
Safe and idempotent are not the same word
Two properties describe every method, and mixing them up is the most common miss on this topic.
Safe means the method doesn’t change server state. It’s read-only. GET, HEAD, and OPTIONS are safe, so a caching proxy or a search-engine crawler can hit them freely with no side effects. If a GET deletes something, someone has built a bug and a security hole in the same line of code.
Idempotent means calling the method once or five times leaves the server in the same state. Every safe method is idempotent, but the reverse doesn’t hold. DELETE changes state, so it isn’t safe, yet it is idempotent: delete a resource, then delete it again, and the end state is identical because the thing is already gone. The second call often returns 404, and that’s what trips people up. Idempotency is about the resulting state, not about getting the same response code every time.
PUT is idempotent because it replaces the whole resource with the payload you sent. Send it twice, you get the same stored object. POST is neither safe nor idempotent, which is why a double-submitted checkout form can charge a card twice.
PATCH is the one candidates get wrong. It’s idempotent only when the change is absolute. Setting {"status": "shipped"} lands in the same place no matter how often you apply it. A relative change like an increment-view-count patch does not, so that PATCH is not idempotent, and a client retry after a dropped connection quietly inflates the number.
The methods in one grid
| HTTP method | Safe (read-only) | Idempotent | Cacheable | Sends a body | What it’s for |
|---|---|---|---|---|---|
| GET | Yes | Yes | Yes | No | Read a resource |
| HEAD | Yes | Yes | Yes | No | Read only the headers, no body |
| OPTIONS | Yes | Yes | No | No | Ask which methods and CORS rules apply |
| POST | No | No | Rarely | Yes | Create a resource or trigger a non-repeatable action |
| PUT | No | Yes | No | Yes | Replace a resource wholesale |
| PATCH | No | Sometimes | No | Yes | Apply a partial modification |
| DELETE | No | Yes | No | Optional | Remove a resource |
The cacheability column has a caveat worth saying out loud in an interview. A POST response can technically be cached when the server sends explicit freshness headers, but almost nobody relies on that, so treat it as a no in practice.
Status codes by their first digit
The leading digit is the whole read for a client. 2xx succeeded. 3xx means go somewhere else to finish. 4xx is the caller’s fault. 5xx is the server’s fault. There’s also 1xx, informational and rarely seen, though 103 Early Hints has started showing up in production to let a browser preload assets while the real response is still being assembled.
This grouping isn’t trivia. A client library decides whether to retry based on the class. Retrying a 4xx is usually pointless because the request itself is wrong. Retrying a 5xx or a 429 with backoff is often correct. Get the class wrong and clients either hammer a struggling server or give up on a request that would have worked on the second try.
Picking the right 2xx
200 OK is the default success. Reach past it when the extra precision carries information. 201 Created says a new resource now exists, and you should return its address in a Location header so the client isn’t left guessing the URL. 202 Accepted means you took the request but haven’t finished it, which is what you return for anything queued or processed in the background, like a video transcode or a bulk import. 204 No Content says it worked and there’s deliberately nothing in the body, common for a DELETE or a PUT that doesn’t echo back the updated object.
Redirects: 301, 302, 307, and 308
The confusion here is permanent versus temporary, crossed with whether the method survives the redirect. 301 Moved Permanently tells browsers and search engines to update their links, and it gets cached aggressively, so a wrong 301 is painful to walk back. 302 Found is temporary. The catch with both is that older clients would switch a POST to a GET when following them, which silently breaks form submissions. 307 Temporary Redirect and 308 Permanent Redirect exist to fix exactly that: they preserve the original method and body. If a redirect has to keep a POST a POST, reach for 307 or 308.
304 Not Modified is the odd member of the family. It answers a conditional GET: the client sent an If-None-Match with an ETag, the resource hasn’t changed, so the server skips the body and tells the client to use its cached copy. It’s the backbone of HTTP caching and a natural follow-up once you mention ETag.
The 4xx codes interviewers actually probe
This is where the real questions live, because choosing between two plausible 4xx codes shows whether you understand the semantics or just memorized a list.
400 Bad Request is the catch-all for malformed input: broken JSON, a missing field the parser choked on. 401 Unauthorized and 403 Forbidden are the pair to get right. 401 means the server doesn’t know who you are, so your credentials are missing or invalid and you should authenticate. 403 means it knows exactly who you are and you still can’t have this. Some APIs deliberately return 404 instead of 403 so they don’t reveal that a resource exists at all, a reasonable choice for private repos or paid tiers.
405 Method Not Allowed fires when the route exists but not for that verb, and the spec says you should list the valid ones in an Allow header. 409 Conflict is the one to name when an interviewer asks about concurrent writes: two clients edit the same record, you version it with optimistic concurrency, and the write that arrives with a stale version loses and gets a 409. 422 Unprocessable Entity covers a request that parsed fine but fails a business rule, like an email that’s syntactically valid but already registered. Whether you separate 400 from 422 is a genuine design debate, so pick a line and be ready to defend it.
429 Too Many Requests is rate limiting, and the detail that earns the point is the Retry-After header telling the client how long to back off. 410 Gone is a firmer 404: the resource existed and was intentionally removed, so stop asking for it.
The way these show up in a live round:
- “A client PUTs the same order twice because the first response timed out. What happens, and what should?”
- “Your endpoint returns 200 with an error message buried in the JSON body. Why is that a problem?”
- “When would you return 403 versus 404 for a resource the caller isn’t allowed to see?”
- “A load balancer sits in front of your service and clients are seeing 502s. Where do you look first?”
5xx: 500, 502, 503, and 504 are different bugs
Lumping the server errors together wastes a chance to show you’ve operated real systems. 500 Internal Server Error is the generic unhandled exception, your code threw and nobody caught it. 502 Bad Gateway means a proxy or load balancer reached your app and got back something invalid, often because the upstream process crashed or returned garbage. 503 Service Unavailable means the server is up but refusing work right now, overloaded or in maintenance, and it should carry a Retry-After. 504 Gateway Timeout means the proxy waited for an upstream response that never arrived in time.
That last split answers the load-balancer question above. A 502 points at the app process itself, which crashed or sent a malformed reply. A 504 points at latency, so the app is alive but too slow, or a dependency it calls is dragging. Knowing which one you’re staring at tells you whether to open crash logs or go hunt a slow query.
Idempotency keys, the follow-up to POST
Once you’ve said POST isn’t idempotent, a sharp interviewer pushes back: so how does a payment API let clients retry safely? The answer is an idempotency key. The client generates a unique value, usually a UUID, and sends it in an Idempotency-Key header. The server records the result against that key on first use and returns the same stored result for any repeat, so a charge retried after a network blip settles the account exactly once. Stripe popularized the pattern, and it’s now standard for anything that moves money or creates records you can’t afford to duplicate.
That’s the thread running under all of it. The status code and the method aren’t decoration on top of your logic. They’re the contract a client reads to decide whether to retry, cache, redirect, or show an error to a user. Choose them as if the caller is a stranger who will do exactly what the numbers tell them, because a well-written client does.
Keep sharpening your system design:
