skip to content

Is the HTTP PATCH method idempotent? Explain what determines the answer and how you would make a PATCH endpoint safe to repeat.

level: middleimportance: should knowfreq 45%

answer

  1. RFC 5789: PATCH neither safe nor idempotent
  2. Merge Patch assignment = idempotent
  3. JSON Patch add-to-array / remove-index / increment = not
  4. If-Match + ETag → replay gets 412
  5. Middleware sees the method, not your handler

basics

~20 s

Not by definition. RFC 5789 leaves PATCH non-idempotent because the patch document decides: setting a field to a fixed value repeats harmlessly, but appending to an array or incrementing a number does not. An individual endpoint can be idempotent — but generic clients must not assume it.

solid answer

~50 s

PATCH is **not** idempotent as a method. RFC 5789 explicitly declines to require it, because idempotency depends on the patch document rather than the method. - `{"status":"shipped"}` as JSON Merge Patch (RFC 7386) is an absolute assignment — applying it twice gives the same state, so that request is idempotent. - JSON Patch (RFC 6902) `{"op":"add","path":"/tags/-","value":"vip"}` appends on every application, so two deliveries produce two tags. - Any relative operation — `{"op":"increment","path":"/balance","value":10}` — is non-idempotent by construction. To make repeats safe I would (a) prefer absolute assignments over relative ops in the patch format, and (b) gate the request with a precondition — `If-Match` against the resource's current ETag — so a replayed PATCH after the first one succeeded fails with 412 Precondition Failed rather than applying twice. Even when an endpoint is idempotent in practice, generic middleware still won't auto-retry PATCH, because the guarantee is made per method.

code

http · 13 lines
http
PATCH /orders/7 HTTP/1.1
Content-Type: application/merge-patch+json

{"status":"shipped"}

>>> idempotent: status is 'shipped' after any number of applications

PATCH /orders/7 HTTP/1.1
Content-Type: application/json-patch+json

[{"op":"add","path":"/tags/-","value":"vip"}]

>>> NOT idempotent: each application appends another 'vip'

go deeper

for a junior

Say PATCH is not idempotent by definition and give one example each way: setting a field is repeatable, appending to a list is not.

for a middle

Name the patch formats (merge-patch vs JSON Patch), explain that the document decides, and mention If-Match as the guard that turns a replay into 412.

for a senior

Argue the design: keep patch operations absolute, model additive changes as PUT on their own URI, and separate endpoint-level idempotency from the method-level guarantee that middleware relies on.

for a principal

Discuss it as an API-surface policy — which patch dialect the organization accepts, whether preconditions are mandatory on mutating endpoints, and where relative operations are unavoidable and therefore need an explicit deduplication contract.

## What the specs actually say PATCH was added by RFC 5789 for partial modification of a resource; RFC 9110 keeps its semantics unchanged. The spec states that PATCH is **neither safe nor idempotent**. That is a statement about the method as a whole — the guarantee that generic clients and intermediaries are entitled to rely on — and the reason is straightforward: unlike PUT, which carries a complete replacement representation, PATCH carries a *set of instructions*, and whether repeating instructions converges depends on the instructions. ## The three cases **Absolute assignment — idempotent.** JSON Merge Patch (RFC 7386) with `Content-Type: application/merge-patch+json` and body `{"status":"shipped"}` says "the status field is now this value". Apply it once, twice, ten times: the resource ends identical. Most CRUD-style PATCH endpoints fall here, which is why so many engineers wrongly believe PATCH is idempotent in general. **Relative or positional operations — not idempotent.** JSON Patch (RFC 6902), `Content-Type: application/json-patch+json`, is an operation list. `{"op":"add","path":"/tags/-","value":"vip"}` appends to the end of an array every time it is applied — two deliveries, two identical tags. `{"op":"remove","path":"/items/0"}` removes whatever is currently first, which is a different element on the second call, exactly like `DELETE /queue/head`. Any endpoint accepting `increment`, `append`, `push` or `adjust by delta` is non-idempotent by construction. **Conditional operations — idempotent by a different mechanism.** JSON Patch's `test` op (`{"op":"test","path":"/status","value":"pending"}` followed by the mutation) makes the whole patch fail with 409 if the precondition no longer holds. The second application fails instead of applying, so the state still converges. This is idempotency achieved by guarding rather than by the operation's own algebra. ## Why the method-level answer still matters A candidate who says "my PATCH endpoints are all merge-patch, so PATCH is idempotent" is making a category error. HTTP's method properties exist so that software that has never read your API documentation — connection pools, HTTP/2 client stacks, reverse proxies, service meshes — can decide whether to automatically retry a request whose response was lost. Those components look at the method only. Because PATCH is not declared idempotent, they will not auto-retry it, no matter how idempotent your particular handler is. Any retry of PATCH has to be a deliberate, application-aware decision. ## Making PATCH safe to repeat Three techniques, roughly in order of preference: **1. Design the patch format to be absolute.** If every operation you accept assigns a value rather than adjusts one, repetition is harmless by construction. This is the cheapest and most robust option and it is available for the large majority of real endpoints. If you need an "add a tag" operation, model it as set-semantics ("the tags are now exactly this set") or as its own resource (`PUT /orders/7/tags/vip`, which is idempotent because PUT is). **2. Gate with a precondition.** Send `If-Match: "<etag>"` with the ETag the client last saw. The first PATCH succeeds and the resource's ETag changes; a replay of the identical request now fails **412 Precondition Failed** instead of applying a second time. This converts an unsafe repeat into a clean, detectable no-op, and it simultaneously protects against lost updates from concurrent writers. Its limitation for retry purposes: a client that receives 412 cannot tell "my earlier attempt landed" from "someone else changed the resource", so it must re-read to decide. **3. Application-level deduplication.** For genuinely relative operations that cannot be reshaped — a balance adjustment, an inventory decrement — the only remaining option is for the server to recognize a repeated logical operation and not apply it twice. That is a separate contract layered on the endpoint, and it must be documented explicitly, because no generic client can infer it. ## What a strong answer sounds like "PATCH is not idempotent as a method — RFC 5789 says so — because the patch document decides. Merge-patch style assignments are idempotent; JSON Patch `add`-to-array, positional `remove`, and any increment are not. I make repeats safe by keeping the patch format absolute and by requiring `If-Match`, so a replay gets 412 rather than applying twice. And I don't rely on my endpoint's idempotency for transport-level retries, because middleware only sees the method."

  • Your PATCH endpoint only accepts merge-patch assignments, so every request is idempotent. Can an HTTP client library now auto-retry it?
    Not on its own. Generic clients, proxies and HTTP/2 stacks decide whether to auto-retry from the method alone, and PATCH is not declared idempotent, so they will not. Retrying has to be an explicit application-level decision by code that knows your endpoint's contract.
  • If a replayed PATCH gets 412 Precondition Failed, how should the client interpret it?
    As 'the resource is no longer in the state you based this change on' — which could mean your earlier attempt actually succeeded, or that another client wrote in between. The client cannot distinguish those from the status alone, so it should re-read the resource, compare against its intent, and only re-issue with the fresh ETag if the change is still needed.
  • How would you model 'add a tag to an order' so it is idempotent?
    Give the tag its own URI and use PUT: `PUT /orders/7/tags/vip` is idempotent because the end state — that tag present — is the same after any number of calls. Alternatively express the patch with set semantics ('tags are now exactly this list') rather than an append operation.

saying these in an interview costs you the question

  • Stating flatly that PATCH is idempotent because it is 'just a partial PUT'.
  • Assuming a merge-patch-only endpoint makes the PATCH method idempotent for generic clients.
  • Not recognizing that JSON Patch add-to-array and remove-by-index break convergence.
  • Believing 412 Precondition Failed means the request definitely never applied earlier.
  • Confusing PATCH with PUT semantics — PUT carries a full replacement, PATCH carries instructions.

context