skip to content

When you design a write endpoint in an HTTP API, how do you choose between the PUT, PATCH and POST methods, and what does each one promise the client?

level: juniorimportance: must knowfreq 82%

answer

  1. POST = process, server picks URL
  2. PUT = full representation, idempotent
  3. PATCH = diff document, needs media type
  4. Omitted field in PUT = deleted
  5. PUT needs a client-known URL

basics

~20 s

POST creates a resource or runs a process; the server picks the URL and repeating it repeats the effect. PUT sends the complete new representation to a URL you already know; repeating it is harmless. PATCH sends only the fields to change.

solid answer

~50 s

Three different promises: - **POST /orders** — "process this". The client does not know the resulting URL; the server creates one and returns `201 Created` with a `Location` header. Sending it twice creates two orders. - **PUT /orders/42** — "make the resource at this URL look exactly like this body". The body is the *whole* representation; anything omitted is removed or reset. Because the final state depends only on the body, sending it twice leaves the same state, so clients and proxies may safely retry it. - **PATCH /orders/42** — "apply this change document to the existing resource". The body is a diff, not a representation, so it needs its own media type (`application/merge-patch+json` or `application/json-patch+json`). PATCH is not idempotent in general — `{"op":"add","path":"/tags/-"}` appends every time. Rule of thumb: client knows the URL and has the full object → PUT; client has a couple of changed fields → PATCH; creation with a server-assigned identity or a non-CRUD action → POST.

go deeper

for a junior

Recall the three promises cleanly: POST creates with a server-chosen URL, PUT replaces the whole resource, PATCH changes part of it. Mention that PUT is safe to retry.

for a middle

Explain that PUT's idempotency follows from the body being a complete representation, and that omitted fields get wiped. Name the patch media types.

for a senior

Tie the choice to client behaviour: retry storms, SDK generation, and the field-clobbering bug that partial PUT bodies cause when two teams evolve the schema.

for a principal

Frame it as a platform-wide contract policy — do all services accept PATCH, in which format, and how is retry safety guaranteed uniformly rather than per-endpoint.

## The question each method answers A write method is part of the *contract*: it tells the client what will happen and what is safe to do twice. Picking one is not style — it changes what a retrying client, an SDK generator, and an on-call engineer may assume. ## POST — "process this representation" POST is the general-purpose method. The target URL is a *collection* or a *processing endpoint*, not the thing being created. The server decides the identity of what it makes and tells the client where it went: ``` POST /orders → 201 Created Location: /orders/42 ``` POST carries no idempotency promise. If the client times out and retries, it may create a second order. That is why platforms bolt an idempotency key onto POST — but that is an added protocol on top, not something POST gives you. ## PUT — "the resource at this URL is now exactly this" PUT targets the resource itself, and the body is a **complete representation**. The server's job is to make the stored state match the body. Two consequences follow directly: 1. **Idempotent.** Final state depends only on the request body, not on how many times it arrived. A client that times out can simply resend. 2. **Omission means removal.** If the stored order has `note: "gift"` and your PUT body has no `note`, the note is gone. This is the number-one source of production bugs when a UI form only knows about half the fields. PUT also means the *client* must know the URL beforehand. That is only possible when the client can produce or already knows the identifier — a natural key (`PUT /users/alice/settings`), or a client-generated UUID. ## PATCH — "apply this change document" PATCH exists because full replacement is expensive and dangerous when the client only wants to change one field. The crucial subtlety: the PATCH body is **not** a partial resource in the abstract — it is a document in a patch format, and the format is declared by `Content-Type`. The two standard ones are JSON Merge Patch (RFC 7386, `application/merge-patch+json`) and JSON Patch (RFC 6902, `application/json-patch+json`). Many real APIs instead accept plain `application/json` and define "present fields are updated, absent fields untouched" in prose; that works, but it is a house convention and it makes `null` ambiguous. PATCH is **not** idempotent by definition. A merge patch usually is (it sets absolute values); a JSON Patch with `add` to an array end, or a `test`-then-`copy` sequence, is not. Contracts that need retry-safety pin it down explicitly, often with a conditional request or an idempotency key. ## Choosing in practice - Creating something whose id the server assigns → **POST** to the collection, `201` + `Location`. - Creating or overwriting something at a client-known URL → **PUT** (this is the upsert case). - Changing one or two fields of a large resource, or a resource whose full representation the client cannot reconstruct → **PATCH**. - An operation that is not "set state" at all — cancel, refund, re-index → **POST** to an action endpoint; PUT/PATCH would lie about the semantics. ## What interviewers listen for They want to hear *why*: idempotency and "full representation vs diff". Candidates who only say "PUT updates, POST creates" miss that PUT can create, that PATCH needs a patch format, and that POST's lack of idempotency is the reason retry infrastructure exists.

  • Is PATCH idempotent?
    Not by definition. A JSON Merge Patch that sets absolute field values usually is idempotent in effect, but a JSON Patch operation such as appending to an array (`"op":"add","path":"/tags/-"`) changes state on every replay. If your contract needs retry safety on PATCH, you must add it — via a conditional request or an idempotency key — and say so in the docs.
  • Can PUT create a resource that does not exist yet?
    Yes. PUT means "make the state at this URL equal this body", which includes creating it. That is the upsert case, and the server should answer `201 Created` when it created and `200`/`204` when it replaced. It only works when the client can know the URL in advance, i.e. with client-generated or natural identifiers.

saying these in an interview costs you the question

  • "POST creates, PUT updates" — PUT also creates, and POST covers any processing, not just creation
  • Treating a PATCH-style partial body as a valid PUT body, silently wiping omitted fields
  • Claiming PATCH is idempotent because it is a small change
  • Assuming PUT is safe (side-effect-free) because it is idempotent — idempotent means repeatable, not read-only
  • Sending a partial JSON object with Content-Type application/json and calling it a standard patch format

context