Skip to content

HTTP method semantics, safe, idempotent, PUT vs PATCH vs POST

A method is a promise about server state: safe changes nothing, and idempotent means once or five times ends the same.

Predict first

Before you read: predict

A box-office app holds seat A12 for cart c-4 with PUT /carts/c-4/seats/A12 and the body {"hold_minutes": 10}. The server stores the hold, but the connection drops before its reply arrives, so the app sends the identical request again. The server log shows 201 Created for the first request and 200 OK for the second.

Did the repeat keep PUT's idempotency promise?

Pick one

The 30-second version

Safe methods (GET, HEAD, OPTIONS) ask the server to change nothing. Idempotent methods (PUT, DELETE and the safe ones) promise that repeating a request leaves the server state as one request did; the replies may differ. So a client can resend them after a lost reply. POST, and a PATCH that adds or increments, can apply twice.

A method is a promise about server state: safe changes nothing, and idempotent means once or five times ends the same.

How it works

Step 1 of 14

Safe (GET, HEAD, OPTIONS, TRACE): the client asks for no state change. Logging or billing the server does on its own does not count.
In the kitchen
  • "What's on my Friday order?": the florist only reads it out → GET, safe (step 1)

Analogy: Phoning in orders to a florist

You phone the florist about your standing Friday order. The line is bad, so sometimes you cannot tell whether they heard you.

At the florist's phone In the system
"What's on my Friday order?": the florist only reads it out GET, safe (step 1)
"Make Friday's order twelve white tulips": said twice, still twelve white tulips PUT (steps 3, 7)
the line crackles before the florist answers, so you say it again resending after a lost reply (step 5)
"Cancel Friday's order" twice; the second time: "there's nothing to cancel" DELETE: another reply, the same state (step 4)
"Send my mother a bouquet" twice: two bouquets arrive POST (step 6)
"make the ribbon blue" against "add three roses" a patch that sets against one that adds (step 9)
after "twelve white tulips", the vase you ordered last week is off the order PUT drops what it leaves out (step 7)
your partner changes the order to lilies; then your crackly repeat of "tulips" gets through a delayed replay (step 11)

In real terms: before letting anything resend a request automatically, check that the server's state after two copies equals its state after one.

Breaks down when: you picture the florist recognising your voice and ignoring a second "send a bouquet". A server does not do that unless you give it a way to (step 14).

Contrasting case: a request whose reply arrived. Nothing is resent, so idempotency makes no difference to it.

Examples, run for real

4.1 ExamHall: submitting an attempt

EXH-1380: the candidate app submitted with POST /attempts/a-7/submit, which recorded a submission and queued grading on every call. On flaky exam-hall Wi-Fi, a lost reply was resent. MR !1580 makes the submission a resource.

4.1 submit attempt a-7; the first reply is lost and the client sends the same request again
  as shipped   POST /attempts/a-7/submit        server answered 201, 201 · client saw lost, then 201 Location /submissions/2 · submissions 2 · grading jobs 2
  merge patch  PATCH /attempts/a-7              server answered 200, 200 · client saw lost, then 200 · submissions 0 · grading jobs 1
  MR !1580     PUT /attempts/a-7/submission     server answered 201, 204 · client saw lost, then 204 · submissions 1 · grading jobs 1

The POST ran twice (step 6). The PUT and the merge patch {"status":"submitted"} can be repeated, because both handlers queue grading only when the state first changes (steps 10, 12).

4.2 Each method, sent once and then retried

4.2 each request once, then again after a lost reply: is the server state the same as after one?
  GET /attempts/a-7                                                           replies 200 then 200 · same state: true
  PUT /attempts/a-7/answers/q4 {"text":"Demand falls"}                        replies 201 then 204 · same state: true
  DELETE /attempts/a-7/flags/q9                                               replies 204 then 404 · same state: true
  PATCH merge-patch+json {"note":"seat 14"}                                   replies 200 then 200 · same state: true
  PATCH json-patch+json [{"op":"add","path":"/reviewed_by/-","value":"p-3"}]  replies 200 then 200 · same state: false (reviewed_by ["p-3"] vs ["p-3","p-3"])
  POST /attempts/a-7/extra-time {"minutes":5}                                 replies 200 then 200 · same state: false (extra_minutes 5 vs 10)
  the server's own access log for GET: 1 row after one request, 2 after the retry (not part of the resource state)
  merge patch on a list: reviewed_by ["p-1"], then {"reviewed_by":["p-3"]} sent twice -> reviewed_by ["p-3"] (the list is replaced whole)

Replies differ on the PUT and the DELETE, yet the state matches (step 4). Replies match on the append and the POST, yet the state does not (steps 6, 9). The access log is the server's own business (step 1). A merge patch that names a list repeats safely, but it replaces the whole list: "p-1" is gone (step 9).

4.3 What idempotent does not promise

4.3 answer q4 from two devices: the phone's reply is lost, the laptop saves, then the phone retries
  phone PUT "Demand falls" lost · laptop PUT "Demand falls as prices rise" 204 · phone retry 204
  stored answer: "Demand falls" (the laptop's newer text is gone)

Each PUT was idempotent, but a late repeat undid a newer write (step 11). Guarding against that is api-optimistic-concurrency-etag's job.

Try it

Answer first, then check. Nothing is saved.

Question 1 of 5

A catalog API implements PUT as RFC 9110 defines it, with no extra validation. Product p-2 is {"name":"Desk lamp","price_cents":1500,"stock":40}. An admin tool changes the price with PUT /products/p-2 and the body {"name":"Desk lamp","price_cents":1299}.

What does GET /products/p-2 return next?

Pick one

Question 2 of 5

A wallet API's top-up handler:

Over flaky mobile connections, some wallets are credited twice for one top-up.

Which is the bug?

Code for this question
// PUT /wallets/:wallet/topups/:topupId   body {"amount_cents": 500}const r = await db.query(  'INSERT INTO topups VALUES ($1, $2, $3) ON CONFLICT (id) DO NOTHING', [topupId, wallet, amount]);await db.query('UPDATE wallets SET balance_cents = balance_cents + $2 WHERE id = $1', [wallet, amount]);return { status: r.rowCount ? 201 : 204 };
Pick one

Question 3 of 5

A chat app invites a guest with POST /rooms/r-1/invites and {"email":"kai@example.test"}. The handler saves the invite; a worker sends the invitation email later. The app must show a working "Revoke" button for the new invite as soon as the reply arrives.

How should the handler reply?

Pick one

Question 4 of 5

A box office's reminder email carries a link, GET /fans/{id}/sms-reminders/toggle?t=<token>; opening it flips the fan's SMS reminders and shows "SMS reminders switched". Two complaints arrive: fans who never opened the email find reminders on, and fans who tapped the link to turn them on find them still off.

Which is the bug?

Pick one

Question 5 of 5

An admin tool tags product p-7 with PATCH /products/p-7 and the JSON Patch [{"op":"add","path":"/tags/-","value":"sale"}]. Its SDK resends any request whose reply is lost. After a flaky connection, p-7's tags read ["new","sale","sale"].

Which fix leaves p-7 tagged "sale" once, with its other tags kept?

Pick one

Go deeper

Pro tips
  1. Let a client library resend only idempotent requests. Configure its retry for GET, PUT and DELETE; give POSTs an idempotency key first. When: adding automatic retries to an SDK or a service client (steps 3, 5, 14).
  2. Tie side effects to the change of state, not to the call. Queue work, send mail or move money only when the row is first created or its status first changes. When: any PUT, DELETE or setting PATCH handler with a side effect (step 10).
Source
  • RFC 9110, HTTP Semantics, §9.2.1 Safe Methods: https://www.rfc-editor.org/rfc/rfc9110.html#section-9.2.1 · fetched 2026-09-28. Safe means "the client does not request, and does not expect, any state change on the origin server"; "most servers append request information to access log files ... and that is considered safe"; "GET, HEAD, OPTIONS, and TRACE methods are defined to be safe"; an unsafe action reachable by GET causes "unfortunate side effects when automated processes perform a GET on every URI reference for the sake of link maintenance, pre-fetching, building a search index, etc.". Valid as of 2026-09-28.
  • RFC 9110 §9.2.2 Idempotent Methods: https://www.rfc-editor.org/rfc/rfc9110.html#section-9.2.2 · fetched 2026-09-28. "the intended effect on the server of multiple identical requests with that method is the same as the effect for a single such request"; "PUT, DELETE, and safe request methods are idempotent"; a server "is free to log each request separately"; the request "can be repeated automatically if a communication failure occurs before the client is able to read the server's response", with "the same intended effect, even if the original request succeeded, though the response might differ"; "A client SHOULD NOT automatically retry a request with a non-idempotent method unless it has some means to know that the request semantics are actually idempotent" (steps 3–5, 14). Valid as of 2026-09-28.
  • RFC 9110 §9.3.3 POST, §9.3.4 PUT, §9.3.5 DELETE: https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.3 · fetched 2026-09-28. POST: the target "process the representation enclosed in the request according to the resource's own specific semantics"; after creating, the server "SHOULD send a 201 (Created) response containing a Location header field". PUT: "the state of the target resource be created or replaced with the state defined by the representation"; "MUST inform the user agent by sending a 201 (Created)" when it creates, and "MUST send either a 200 (OK) or a 204 (No Content)" when it modifies; PUT also says the state change may not be observable, "since the target resource might be acted upon by other user agents in parallel" (step 11). DELETE: the server SHOULD send 202 "if the action will likely succeed but has not yet been enacted", 204 "if the action has been enacted and no further information is to be supplied", or 200 with a representation (steps 6–7, 13). Valid as of 2026-09-28.
  • RFC 9110 §15.3.2 201, §15.3.3 202, §15.3.5 204: https://www.rfc-editor.org/rfc/rfc9110.html#section-15.3.2 · fetched 2026-09-28 (plain text re-read 2026-09-28 for Batch-W2C4). 201: "the request has been fulfilled and has resulted in one or more new resources being created. The primary resource created by the request is identified by either a Location header field in the response or, if no Location header field is received, by the target URI". 202: "accepted for processing, but the processing has not been completed"; its representation "ought to describe the request's current status and point to (or embed) a status monitor". 204: "the server has successfully fulfilled the request and that there is no additional content to send in the response content". §9.3.3 POST (https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.3): "If one or more resources has been created on the origin server as a result of successfully processing a POST request, the origin server SHOULD send a 201 (Created) response containing a Location header field". Valid as of 2026-09-28.
  • "the enclosed entity contains a set of instructions describing how a resource currently residing on the origin server should be modified"; "PATCH is neither safe nor idempotent"; "A PATCH request can be issued in such a way as to be idempotent" (steps 8–9). Valid as of 2026-09-28.
  • The MergePatch pseudocode replaces any non-object value in the target, arrays included, and a null removes a member; the notes under it say: "If the patch is anything other than an object, the result will always be to replace the entire target with the entire patch. Also, it is not possible to patch part of a target that is not an object, such as to replace just some of the values in an array". "If the "-" character is used to index the end of the array ... this has the effect of appending the value to the array". Valid as of 2026-09-28.
  • Model, not observed: the link scanner in D1 (a client that GETs every link in an email before the fan opens it and submits no form) is a model of the automated processes RFC 9110 §9.2.1 names; no real mail scanner was run. P3's app is a model that reads each reply by the RFC 9110 §15.3 meanings quoted above (201: bind Revoke to Location; 202: not done, read the status monitor; 204: nothing to bind); the handlers, rows and requests are real.

Checked against the tools and docs as of 2026-09-28.