Designs by Duhart All writing

·8 min read·backend · webdevelopment · api · restapi · http · softwareengineering · codinginterview · systemdesign · programming · devcommunity

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.

Cover slide on a dark background: 7 HTTP METHODS  EVERY BACKEND ENGINEER  SHOULD KNOW. What each one promises, and the trap that catches people in interviews. The numbered list: GET; POST; PUT; PATCH; DELETE; HEAD; OPTIONS; Cheat sheet. A gold band reads Save this for your next interview.

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

Cover slide on a dark background: 7 HTTP METHODS  EVERY BACKEND ENGINEER  SHOULD KNOW. What each one promises, and the trap that catches people in interviews. The numbered list: GET; POST; PUT; PATCH; DELETE; HEAD; OPTIONS; Cheat sheet. A gold band reads Save this for your next interview.
The carousel this article expands on: seven methods, one cheat sheet.

The 36 second version

All seven methods and the cheat sheet in 36 seconds, captions on.
Poster frame of the explainer video: Is delete idempotent if the second call returns 404 ?
All seven methods and the cheat sheet in 36 seconds, captions on. Watch the video: https://designsbyduhart.org/blog/http-methods-every-backend-engineer-should-know/

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

Slide 2 of 10: READ, NEVER CHANGE. Fetch a representation of a resource. Safe, idempotent and cacheable. Why it matters: Browsers prefetch, crawlers follow links and CDNs cache GETs, all assuming nothing changes. Put a side effect behind GET and something you never called will trigger it. A code card (bash) shows: API=https://api.example.com curl -i $API/orders/42  HTTP/1.1 200 OK Content-Type: application/json Cache-Control: max-age=60 ETag: "v7" Interview trap: Can GET have a body? RFC 9110 gives it no defined meaning and some servers reject it. For big read queries, 2026 brought QUERY (RFC 10008).
Safe, idempotent, cacheable. The trap: a body on a 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

Slide 3 of 10: CREATE OR PROCESS. Send data for the server to process. Not safe, not idempotent. Why it matters: Retry a timed out POST and you can create two orders or charge a card twice. Make it safe to retry with an idempotency key the server remembers. A code card (bash) shows: curl -i -X POST $API/orders \ -H "Idempotency-Key: 8f1c0e7a" \ -d '{"sku":"A1","qty":1}'  HTTP/1.1 201 Created Location: /orders/43 In production: My Stripe webhook ingest writes the event id before any handler runs. A replayed event comes back duplicate: true and nothing is charged twice.
Not safe, not idempotent. Make retries safe with an idempotency key.

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.

python
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

Slide 4 of 10: REPLACE THE WHOLE THING. Create or replace the resource at a URI you already know. Idempotent. Why it matters: Send the same PUT ten times and the server ends in the same state as after one. That is why clients and proxies may retry it safely. A code card (bash) shows: curl -i -X PUT $API/users/7 \ -d '{"name":"Ada","email":"ada@x.io"}'  HTTP/1.1 200 OK   # 201 if it was new Interview trap: Leave a field out of a PUT and you have told the server to remove it. PUT replaces. It does not merge.
Idempotent full replacement. The trap: a missing field is a deleted field.

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. A null value 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 use test as 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

Slide 5 of 10: CHANGE ONLY THIS PART. Apply a partial update (RFC 5789). Not idempotent by definition. Why it matters: You send only what changes, so two clients editing different fields do not overwrite each other the way two full PUTs would. A code card (bash) shows: curl -i -X PATCH $API/orders/43 \ -H "Content-Type: application/merge-patch+json" \ -d '{"status":"shipped","note":null}'  HTTP/1.1 200 OK Interview trap: In JSON Merge Patch (RFC 7396), null means delete the field. Need ops like append or test? That is JSON Patch (RFC 6902).
Partial update. The trap: null deletes in a merge 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.

bash
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

Slide 6 of 10: REMOVE IT. Remove the resource at the URI. Idempotent. Why it matters: Idempotent is about server state, not the response. The first DELETE returns 204, the second may return 404, and the resource is gone either way. A code card (bash) shows: curl -i -X DELETE $API/orders/43 HTTP/1.1 204 No Content  curl -i -X DELETE $API/orders/43 HTTP/1.1 404 Not Found   # still idempotent Interview trap: "The second call returned 404, so DELETE is not idempotent." Wrong, and interviewers ask it on purpose.
Idempotent even when the second call returns 404.

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

Slide 7 of 10: GET, WITHOUT THE BODY. Same headers a GET would return, no content. Safe, idempotent, cacheable. Why it matters: Check that a 2 GB file exists, how big it is and whether it changed, without downloading a byte of it. A code card (bash) shows: curl -I $CDN/video.mp4  HTTP/1.1 200 OK Content-Length: 2147483648 ETag: "a91f" Last-Modified: Tue, 06 Oct 2026 09:00:00 GMT Where it pays off: Pair it with conditional requests. A GET with If-None-Match: "a91f" gets 304 Not Modified and no body while nothing has changed.
Headers only: existence, size and freshness without the download.

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.

bash
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

Slide 8 of 10: WHAT AM I ALLOWED TO DO?. Ask what the server supports. Browsers send it as the CORS preflight. Why it matters: Before a cross origin PATCH, DELETE or JSON POST, the browser asks first. If your API answers OPTIONS with 404 or 401, the real request never leaves the browser. A code card (bash) shows: curl -i -X OPTIONS $API/orders/43 \ -H "Origin: https://app.example.com" \ -H "Access-Control-Request-Method: PATCH"  HTTP/1.1 204 No Content Access-Control-Allow-Methods: GET, PATCH Interview trap: A "CORS error" is usually a preflight your auth middleware rejected. Let OPTIONS through before the auth check.
The CORS preflight. Let it through before your auth check.

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

Slide 9 of 10: MEMORISE THIS TABLE. Safe: no state change. Idempotent: repeat it, same end state. Every safe method is idempotent. A table: METHOD, SAFE, IDEMPOTENT, CACHEABLE; GET, YES, YES, YES; HEAD, YES, YES, YES; OPTIONS, YES, YES, NO; PUT, NO, YES, NO; DELETE, NO, YES, NO; POST, NO, NO, RARELY; PATCH, NO, NO, NO.
Every safe method is idempotent. POST is cacheable in theory, almost never in practice.

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-Location matching 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

Closing slide 10 of 10: Save this. Recap: GET reads and never changes state; POST creates; make retries safe with a key; PUT replaces the whole resource; PATCH changes only what you send; DELETE is idempotent, even on a 404; HEAD is GET without the body; OPTIONS answers the CORS preflight. Your turn: Which one have you seen misused the most in a real API? Full write-up with code at designsbyduhart.org.
The recap card.

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.