7 HTTP Methods Every Backend Engineer Should Know
GET, POST, PUT, PATCH, DELETE, HEAD and OPTIONS: what each one promises, the safe and idempotent rules behind them, and the interview traps that catch people.

The verbs are contracts
Most of us learned HTTP methods as CRUD with different names. GET reads, POST creates, PUT updates, DELETE deletes. That gets you through a tutorial. It doesn't get you through an interview with a backend team, and it definitely doesn't get you through a retry storm.
Each method is a promise. RFC 9110, the current HTTP semantics spec, defines two properties that matter more than the names. A method is safe if the client isn't asking for any change on the server. It's idempotent if sending the same request twice leaves the server in the same state as sending it once. Every safe method is idempotent. Not every idempotent method is safe.
Why care? Because a lot of software you don't control acts on those promises. Browsers prefetch links. Crawlers follow every GET they can find. Proxies and HTTP clients retry idempotent requests when a connection drops. CDNs cache what they're allowed to cache. When your API breaks a promise, those systems don't know, and they do the wrong thing on your behalf.
Here are the seven methods you'll use in almost every API, one at a time, with the trap I'd ask about if I were interviewing you. The carousel slides are included, so you can save the article and skip the reading.
7 HTTP methods every backend engineer should know

The 36 second version

1. GET: read, never change
GET asks for a representation of a resource. It's safe, idempotent and cacheable, and it's the method with the most machinery built around it. That's exactly why a side effect behind a GET is dangerous. The classic story is an admin page with "delete" links that were plain GETs, and a crawler that dutifully followed every one of them. Any time a GET changes state, something you never called will eventually call it.
Use the caching headers. Cache-Control tells caches how long a response is fresh, and an ETag lets clients ask "has this changed?" cheaply (more on that under HEAD).
GET

The interview trap: can a GET have a body? Technically you can send one. RFC 9110 says content in a GET request has no generally defined semantics, can't change the meaning of the request, and might make some implementations reject it. Proxies and caches ignore it. So people who needed a big search query either crammed it into the URL or switched to POST and lost caching and safe retries.
That gap finally has an answer. RFC 10008, The HTTP QUERY Method, was published as a Proposed Standard in 2026. QUERY carries a body like POST but is safe and idempotent like GET. Support in frameworks and proxies is still catching up, so check yours before you rely on it, but it's the right answer to "what should I use for a complex read with a body".
2. POST: create or process
POST asks the server to process the enclosed data according to the resource's own rules. That's deliberately vague, so POST is the catch-all: create an order, start a job, submit a form. It is neither safe nor idempotent.
That last part is where real money gets lost. A client sends a POST to create a payment, the connection drops before the response arrives, and the client has no idea whether the payment happened. Retry, and you may charge twice. Don't retry, and you may lose the order.
POST

The standard fix is an idempotency key. The client generates a unique key per logical operation and sends it in a header. The server stores the key with the result the first time and returns the stored result for any repeat. Stripe made this pattern famous, and the IETF httpapi working group has an Idempotency-Key header draft that writes it down. Response codes to know: 201 Created with a Location header when POST creates something, 202 Accepted when it queued work for later.
The same idea works for webhooks you receive. My storefront's Stripe webhook ingest writes the event id into a table before any handler runs, and that row is the single idempotency guard. A replayed event comes back duplicate: true and nothing runs twice. One lesson from building it: consumers should check whether the event was processed, not whether the row exists. A row that exists but failed halfway still needs processing.
The shape of an idempotent webhook or POST handler: record the key first, act once, return the stored result on a repeat.
def handle(event_id, payload, db):
inserted = db.execute(
"INSERT INTO events (id, status) VALUES (?, 'pending') "
"ON CONFLICT (id) DO NOTHING", (event_id,)).rowcount
status, result = db.execute(
"SELECT status, result FROM events WHERE id = ?", (event_id,)).fetchone()
if not inserted and status == "processed":
return {"duplicate": True, "result": result}
result = process(payload) # the side effect, once
db.execute("UPDATE events SET status = 'processed', result = ? WHERE id = ?",
(result, event_id))
return {"duplicate": False, "result": result}3. PUT: replace the whole thing
PUT says "make the resource at this URI look exactly like this". The client knows the URI, and the body is the complete new state. If the resource didn't exist, the server can create it and answer 201 Created. If it did, 200 OK or 204 No Content.
PUT is idempotent: ten identical PUTs leave the same state as one. That's what lets a client or proxy retry it after a timeout without thinking hard.
PUT

The interview trap: a PUT without the email field doesn't mean "leave email alone". It means "the new resource has no email". Plenty of APIs implement PUT as a merge anyway, which works until a client that doesn't know about a newer field sends a PUT and wipes it. If you want partial updates, that's PATCH.
4. PATCH: change only this part
PATCH (RFC 5789) applies a set of changes to a resource. The body describes the changes, not the whole resource, and its media type says how to read them. Two formats cover most APIs:
- JSON Merge Patch (RFC 7396,
application/merge-patch+json): send an object shaped like the resource with only the fields you want changed. Anullvalue deletes that field. Simple, readable, and it can't express "set this field to null" separately from "remove it". - JSON Patch (RFC 6902,
application/json-patch+json): a list of operations (add,remove,replace,move,copy,test) with JSON Pointer paths. More verbose, but it can append to arrays and usetestas a guard.
PATCH is not idempotent by definition. An operation like "append this item" changes state every time you repeat it. A merge patch that sets fields to fixed values happens to be idempotent, and you can make the rest safe to retry with conditional requests (If-Match with an ETag).
PATCH

JSON Patch for the operations a merge patch cannot express. The test op makes the whole patch fail if someone changed the status first.
curl -i -X PATCH https://api.example.com/orders/43 \
-H "Content-Type: application/json-patch+json" \
-d '[
{"op": "test", "path": "/status", "value": "paid"},
{"op": "replace", "path": "/status", "value": "shipped"},
{"op": "add", "path": "/events/-", "value": "shipped"}
]'5. DELETE: remove it
DELETE asks the server to remove the association between the URI and the resource. It's idempotent, and this is where interviewers love to push. You delete order 43 and get 204 No Content. You send the same DELETE again and get 404 Not Found. Is DELETE idempotent?
Yes. Idempotency is about the state of the server after the request, not the status code of the response. After one DELETE or five, order 43 is gone. Some teams return 204 for the repeat too, which is friendlier for retrying clients; both are defensible. 202 Accepted is right when the deletion is queued and happens later.
DELETE

6. HEAD: GET without the body
HEAD returns the same headers a GET would, with no content. It's safe, idempotent and cacheable, and it's the cheapest way to ask about a resource: does it exist, how big is it, what type is it, when did it last change. Download managers use it to size a file before fetching it in ranges. Link checkers use it so they don't download every page.
HEAD

The pattern it pairs with is the conditional request. Keep the ETag from a previous response and send it back as If-None-Match. If nothing changed, the server answers 304 Not Modified with no body, and you reuse your copy. For writes, If-Match does the reverse: "only apply this if the resource is still at the version I saw", and the server answers 412 Precondition Failed if someone else got there first. That's optimistic concurrency in two headers.
Conditional GET: the second request costs a round trip and no body while the resource has not changed.
curl -i https://api.example.com/orders/42 -H 'If-None-Match: "v7"'
HTTP/1.1 304 Not Modified
ETag: "v7"7. OPTIONS: what am I allowed to do?
OPTIONS asks what communication options a resource supports. On its own it's rarely used. Where you meet it constantly is CORS. When a browser page on app.example.com wants to send a request to api.example.com that isn't a "simple" request (a PATCH or DELETE, a JSON POST, a custom header like Authorization), the browser first sends an OPTIONS preflight with Origin, Access-Control-Request-Method and Access-Control-Request-Headers. Only if the response allows it does the real request go out.
That's the source of most "CORS errors" I've seen. The API's auth middleware runs before the CORS handling, the preflight carries no credentials, the middleware answers 401, and the browser reports a CORS failure on a request that never left. Handle OPTIONS (and answer the preflight) before authentication, and cache the result with Access-Control-Max-Age so the browser doesn't preflight every call.
OPTIONS

The cheat sheet
If you only remember one thing from this post, make it this table. It's straight from RFC 9110 (and RFC 5789 for PATCH).
Safe, idempotent, cacheable

A few notes on the edges:
- CONNECT and TRACE are also standard methods. CONNECT opens a tunnel through a proxy (it's how HTTPS goes through a forward proxy). TRACE echoes the request back for debugging and is usually disabled.
- QUERY (RFC 10008, 2026) is the new one: a safe, idempotent read with a body. Watch for support in your framework, your gateway and your CDN before you depend on it.
- POST is cacheable only when the response has explicit freshness information and a
Content-Locationmatching the target. Almost nothing does this, which is one reason QUERY was needed.
How I'd answer in an interview
If someone asks "what's the difference between PUT and PATCH", don't stop at "full versus partial". Say PUT replaces and is idempotent, so it's safe to retry; PATCH describes changes, isn't idempotent by definition, and you make it safe with a merge patch of fixed values or an If-Match precondition. Then mention what a missing field means in each. That's the answer that shows you've run an API, not just read about one.
Save it for your next interview

Next in the interview series: WebSockets, the concepts every backend engineer should know, from the upgrade handshake to scaling with pub/sub.
More: LinkedIn · Instagram. Portfolio and case studies: designsbyduhart.org.
If any of this saved you an afternoon, Buy me a coffee.