Skip to content

HTTP Methods and Idempotency

TL;DR

REST uses five HTTP methods — GET, POST, PUT, PATCH, DELETE. Each has different guarantees about safety and idempotency. The most important thing to understand: idempotency is an implementation contract, not a magic property of the verb. Getting this nuance right separates junior from senior API designers.

The Five Tools in Your Toolbox

Think of HTTP methods like the five basic tools you'd find in a kitchen.

A knife (GET) is for reading — you use it to inspect, to slice open something and see what's inside, but you never change the food itself. A mixer (POST) creates something new — you throw ingredients in and get something that didn't exist before. A rolling pin (PUT) replaces everything flat — you flatten the entire thing and reshape it completely. A garnish tool (PATCH) makes small adjustments — a sprinkle here, a tweak there, without replacing the whole dish. And a trash can (DELETE) removes something entirely.

Each tool has a purpose. Using POST when you should use PUT is like using a mixer when you need a rolling pin — it might sort of work, but you'll make a mess.

HTTP methods and idempotency matrix

Let's go through each one properly.

GET — Read a Resource

GET retrieves data without changing anything on the server. It's the most common HTTP method by far.

GET /events/123

Response: 200 OK
{
  "id": 123,
  "name": "Taylor Swift - Eras Tour",
  "venue": "SoFi Stadium",
  "date": "2025-08-15",
  "tickets_available": 4200
}

Key properties: - Safe — calling it doesn't change server state. You can call GET a million times and nothing changes. - Idempotent — calling it once gives the same result as calling it ten times. - Cacheable — browsers and CDNs can cache GET responses. - No request body — all parameters go in the URL (path or query string).

GET is the only method that should be truly "free" to call. Never design a GET endpoint that creates, updates, or deletes anything. If clicking a link can change your data, you've made a serious design mistake.

POST — Create a Resource

POST creates a new resource. The server decides what the new resource looks like (including its ID).

POST /events/123/bookings
Content-Type: application/json

{
  "ticket_ids": [789, 790],
  "payment_method": "card_ending_4242"
}

Response: 201 Created
Location: /events/123/bookings/456
{
  "id": 456,
  "event_id": 123,
  "ticket_ids": [789, 790],
  "status": "confirmed",
  "total": 350.00
}

Key properties: - Not safe — it changes server state (creates something). - Not idempotent — calling it twice creates two bookings. This is the duplicate booking problem. - Not cacheable — every POST should be treated as unique. - Has a request body — the data for the new resource goes in the body.

The response should return 201 Created (not 200 OK) and include a Location header pointing to the newly created resource.

The Duplicate Problem

What happens if your network drops after the server processes a POST but before you get the response? You don't know if it worked. So you retry. Now you have two bookings.

This is exactly why POST is not idempotent, and why it matters. We'll talk about idempotency keys later in this lesson — they're the standard solution to this problem.

PUT — Replace a Resource Entirely

PUT replaces the entire resource with what you send. Think of it as "take what's there, throw it away, and put this in its place."

PUT /events/123
Content-Type: application/json

{
  "name": "Taylor Swift - Eras Tour (Updated)",
  "venue": "SoFi Stadium",
  "date": "2025-08-16",
  "tickets_available": 4200,
  "description": "Updated show date"
}

Response: 200 OK
{
  "id": 123,
  "name": "Taylor Swift - Eras Tour (Updated)",
  "venue": "SoFi Stadium",
  "date": "2025-08-16",
  "tickets_available": 4200,
  "description": "Updated show date"
}

Key properties: - Not safe — it modifies server state. - Idempotent — sending the same PUT request ten times produces the same result as sending it once. The resource ends up in the same state regardless. - Has a request body — the complete representation of the resource.

Important nuance: if you send a PUT and omit a field, that field should be removed (or set to its default). PUT means "here is the complete resource" — anything you don't include is gone.

PUT Can Also Create

Here's something that surprises many developers: PUT can create a resource if it doesn't exist. This is explicitly allowed by the HTTP spec.

PUT /events/999
Content-Type: application/json

{
  "name": "New Concert",
  "venue": "Madison Square Garden",
  "date": "2025-12-01"
}

Response: 201 Created  (if event 999 didn't exist)
Response: 200 OK       (if event 999 already existed and was replaced)

The key difference from POST: with PUT, the client specifies the resource ID. With POST, the server assigns the ID. Azure Blob Storage is a real-world example — you PUT a blob to a specific path, and if it doesn't exist, it gets created. If it does exist, it gets replaced.

PATCH — Partial Update

PATCH updates only the fields you send. Everything else stays the same.

PATCH /events/123
Content-Type: application/json

{
  "date": "2025-08-20"
}

Response: 200 OK
{
  "id": 123,
  "name": "Taylor Swift - Eras Tour (Updated)",
  "venue": "SoFi Stadium",
  "date": "2025-08-20",
  "tickets_available": 4200,
  "description": "Updated show date"
}

Only the date changed. Everything else is untouched.

Key properties: - Not safe — it modifies server state. - NOT idempotent — this one surprises people. We'll dig into why below. - Has a request body — only the fields being updated.

PUT vs PATCH: When to Use Which

Scenario Use PUT Use PATCH
You have the complete resource and want to replace it Yes No
You want to change one field out of twenty No Yes
The client should provide all required fields Yes No
The client only knows what changed No Yes
Idempotency is critical for your use case Yes Maybe

In practice, PATCH is far more common in modern APIs. Most updates are partial — "change the user's email" or "update the event date." Sending the entire resource just to change one field is wasteful and error-prone.

Interview Tip

PUT vs PATCH is a classic interview question. The key distinction: PUT replaces the entire resource (send everything), PATCH modifies only what you specify (send the diff). If you say "PUT is for updates and PATCH is for partial updates," interviewers will push you on why that distinction matters — and the answer is idempotency.

DELETE — Remove a Resource

DELETE removes a resource.

DELETE /bookings/456

Response: 204 No Content

Key properties: - Not safe — it changes server state. - Idempotent — deleting something that's already gone doesn't change the server state. The first DELETE returns 204 (success, no content). Subsequent DELETEs might return 404 (not found), but the server state is the same — the resource is gone either way. - Usually no request body — though some APIs do accept one.

The 204 vs 404 distinction on repeated calls is important. Idempotency is about server state, not response codes. Whether you delete it once or five times, the end state is: the resource doesn't exist.

The Complete Methods Table

Method Safe? Idempotent? Cacheable? Has Body? Typical Status
GET Yes Yes Yes No 200 OK
POST No No No Yes 201 Created
PUT No Yes No Yes 200 OK / 201 Created
PATCH No No No Yes 200 OK
DELETE No Yes No Usually No 204 No Content

Memorize this table. It comes up constantly in interviews and design discussions.

Deep Dive: Idempotency Is a Contract, Not Magic

This is the most misunderstood concept in REST API design, and it's worth getting right.

Many developers think idempotency is a property of the HTTP method. "PUT is idempotent" the way "water is wet." You use PUT, and poof — idempotency happens.

That's wrong. Idempotency is an implementation contract. The HTTP spec says PUT should be idempotent, but it's your job as the API developer to make it so. If your PUT endpoint increments a counter every time it's called, you've broken the contract — even though you're using PUT.

Here's how RFC 9110 puts it:

A request method is considered idempotent if the intended effect on the server of multiple identical requests with that method is the same as the effect for a single such request.

The key phrase is "intended effect on the server." Not the response. Not the status code. The server state.

Why PATCH Is Not Idempotent

This catches people off guard because many PATCH operations seem idempotent. Consider:

PATCH /users/123
{ "email": "new@example.com" }

Call this ten times — the email is new@example.com every time. Seems idempotent, right?

Now consider this PATCH:

PATCH /posts/456
{ "op": "append", "path": "/tags", "value": "featured" }

Call this ten times — you get "featured" appended ten times. Definitely not idempotent.

The HTTP spec (RFC 5789) correctly classifies PATCH as not idempotent because the spec for the method itself cannot guarantee idempotency. Some PATCH operations are idempotent in practice ("set email to X"), but others are not ("append to list"). Since the method as a category can't make the guarantee, PATCH is classified as non-idempotent.

Why This Distinction Matters in the Real World

The idempotency classification tells infrastructure — load balancers, proxies, retry logic — what's safe to retry automatically.

If a GET request times out, any proxy can safely retry it. GET is idempotent and safe — retrying can't cause harm.

If a PUT request times out, the proxy could retry it, because PUT is idempotent. The server will end up in the same state either way.

If a POST request times out, the proxy must not retry it automatically, because POST is not idempotent. Retrying could create duplicate resources.

This is why the classification matters far beyond academic correctness. It directly affects how your API behaves under network failures.

RFC 9110 Reference

For the record, here's the complete breakdown per RFC 9110:

Guaranteed Idempotent (per spec) NOT Guaranteed Idempotent
GET POST
PUT PATCH
DELETE
HEAD
OPTIONS
TRACE

POST and PATCH are on the "not guaranteed" side. You can implement them idempotently (and sometimes should), but the spec doesn't promise it.

Idempotency Keys: Making POST Safe to Retry

So POST isn't idempotent, and network failures happen. How do you prevent duplicate bookings?

The solution is an idempotency key — a unique token the client sends with the request. The server uses it to detect duplicates.

POST /events/123/bookings
Idempotency-Key: abc-123-unique-token
Content-Type: application/json

{
  "ticket_ids": [789, 790],
  "payment_method": "card_ending_4242"
}

Here's what happens:

  1. First call: Server sees Idempotency-Key: abc-123-unique-token for the first time. It processes the request, creates the booking, and stores the response alongside the key.
  2. Retry (same key): Server recognizes the key, skips processing, and returns the stored response from the first call.

The client is responsible for generating a unique key (usually a UUID) for each logical operation. If you want to retry the same booking, you send the same key. If you want to make a different booking, you send a new key.

Stripe, PayPal, and virtually every payment API uses this pattern. It's the standard solution for making non-idempotent operations safe to retry.

The Full Picture: Method Selection Flowchart

When you're deciding which method to use, follow this logic:

  1. Are you reading data without changing anything? Use GET.
  2. Are you creating a new resource where the server assigns the ID? Use POST.
  3. Are you replacing a resource entirely (or creating it at a client-specified ID)? Use PUT.
  4. Are you updating specific fields of an existing resource? Use PATCH.
  5. Are you removing a resource? Use DELETE.

When in doubt, ask yourself: "If the network fails and this request gets sent twice, what happens?" If the answer is "something bad" — you need either idempotency guarantees (PUT/DELETE) or an idempotency key (POST).

Interview Tip

Know the idempotency table cold — it comes up in virtually every API design discussion. But more importantly, be ready to explain why idempotency matters (network failures and retries) and how to make non-idempotent operations safe (idempotency keys). That's what separates someone who memorized a table from someone who actually understands distributed systems.

Interview Expectations: Junior vs. Senior

  • Junior/Mid-level: Can list the CRUD mappings (POST for create, GET for read, PUT/PATCH for update). Might mix up PUT and PATCH or fail to explain idempotency clearly.
  • Senior/Staff: Consistently designs idempotent APIs (especially for payments or state changes) using Idempotency-Key headers. Understands that while PUT is idempotent by definition, it requires replacing the entire resource, making PATCH a better choice for partial updates in high-concurrency systems.