1. Library
  2. Computer Networks
  3. Http and the Web
  4. Methods

Updated 9 hours ago

You send a request. The network times out. Did the server process it?

You genuinely don't know. The request might have arrived and succeeded, with only the response lost. Or the request might have died in transit. The network doesn't tell you whether your request succeeded—it tells you whether it heard back. Those aren't the same thing.

Idempotency is how you handle this uncertainty.

What Idempotency Means

An HTTP method is idempotent if making the same request multiple times produces the same server-side effect as making it once.

DELETE is idempotent. Delete a resource once, it's gone. Delete it ten more times—it's still gone. The responses differ (first returns 204 No Content, subsequent return 404 Not Found), but the outcome is identical: the resource doesn't exist.

POST is typically not idempotent. Submit a form three times, you might create three resources. Charge a credit card three times, you bill the customer three times. Each request changes server state in a new way.

The Idempotent Methods

GET

Retrieving a resource doesn't change it. Request the same article a hundred times—the server state remains unchanged.

GET /articles/123 HTTP/1.1

→ Article content (first time)
→ Same article content (hundredth time)

Caveat: Poorly designed APIs add side effects to GET—incrementing view counters, triggering actions, logging. This violates the HTTP specification and creates real problems when proxies, browsers, and load balancers retry GET requests expecting safety.

PUT

Updating a resource to a specific state is idempotent. Set an article's title to "Updated Title" once, it has that title. Set it again—it still has that title.

PUT /articles/123 HTTP/1.1
{"title": "Updated Title", "content": "Updated content"}

→ Article updated (first time)
→ Article already in that state (second time)

DELETE

Removing a resource is idempotent. The resource is gone after the first request. It's still gone after the tenth.

DELETE /articles/123 HTTP/1.1

→ 204 No Content (first time)
→ 404 Not Found (tenth time—but resource is equally gone)

HEAD and OPTIONS

HEAD retrieves metadata without modifying anything. OPTIONS queries capabilities. Neither changes server state.

The Non-Idempotent Methods

POST

Each POST potentially creates new state:

POST /articles HTTP/1.1
{"title": "New Article"}

→ Article #123 created (first request)
→ Article #124 created (second request)
→ Article #125 created (third request)

This is the duplicate submission problem. Network timeouts, impatient users clicking "Submit" multiple times, automatic retries—all create unintended duplicates.

PATCH

PATCH can go either way, depending on the patch format.

Idempotent (setting absolute values):

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

→ Email is now new@example.com (every time)

Non-idempotent (relative changes):

PATCH /users/123 HTTP/1.1
{"balance": {"increment": 10}}

→ Balance increases by 10 (every time)

JSON Merge Patch (RFC 7396) is idempotent. JSON Patch (RFC 6902) can be non-idempotent depending on the operations.

Why This Matters

Safe Retries

With idempotent methods, retry freely:

async function fetchArticle(id) {
  for (let attempt = 0; attempt < 3; attempt++) {
    try {
      return await fetch(`/articles/${id}`);
    } catch (error) {
      if (attempt === 2) throw error;
      await sleep(1000 * (attempt + 1));
    }
  }
}

GET is idempotent. Retrying can't cause harm.

With POST, retrying is dangerous:

// Each retry might create another article
async function createArticle(data) {
  for (let attempt = 0; attempt < 3; attempt++) {
    try {
      return await fetch('/articles', {
        method: 'POST',
        body: JSON.stringify(data)
      });
    } catch (error) {
      // Timeout—did it succeed? Retrying might create duplicates
    }
  }
}

Load Balancers Retry Automatically

When a backend server times out, load balancers retry idempotent requests on another server:

Client → Load Balancer → Server 1 (timeout)
                       → Server 2 (retry—safe for GET/PUT/DELETE)

They don't retry POST without special configuration. They know it's dangerous.

Browsers Protect Users

Refresh a page after submitting a form and you'll see:

"Are you sure you want to resend this form?"

Browsers know POST isn't idempotent. They warn before repeating.

Caching Works

Only idempotent methods can be safely cached. If a request changes server state, cached responses become lies.

GET responses cache by default. POST responses don't.

Making POST Idempotent

Since POST often needs retries, several patterns add idempotency:

Idempotency Keys

The client generates a unique key for each logical operation:

const idempotencyKey = crypto.randomUUID();

fetch('/api/orders', {
  method: 'POST',
  headers: {
    'Idempotency-Key': idempotencyKey,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(orderData)
});

The server tracks keys. Same key twice? Return the original result:

app.post('/api/orders', async (req, res) => {
  const key = req.get('Idempotency-Key');
  const cached = await cache.get(key);
  
  if (cached) return res.json(cached);
  
  const order = await createOrder(req.body);
  await cache.set(key, order, { ttl: 3600 });
  res.status(201).json(order);
});

Now retries are safe.

Client-Generated IDs

Let the client provide the ID:

const orderId = crypto.randomUUID();

fetch(`/api/orders/${orderId}`, {
  method: 'PUT',
  body: JSON.stringify({ id: orderId, items: [...] })
});

PUT to a specific URL is idempotent. Order exists? Update it. Doesn't exist? Create it. Retry produces the same order.

Single-Use Tokens

Embed a token in the form:

<form method="POST" action="/submit">
  <input type="hidden" name="token" value="unique-token-123">
</form>

Server validates and invalidates:

app.post('/submit', (req, res) => {
  if (!validateAndInvalidateToken(req.body.token)) {
    return res.status(400).send('Invalid or expired token');
  }
  processForm(req.body);
  res.redirect('/success');
});

Duplicate submissions fail.

RESTful Design

Use methods according to their semantics:

Idempotent operations → PUT or DELETE

  • Setting a resource to a specific state: PUT
  • Removing a resource: DELETE

Non-idempotent operations → POST

  • Creating with server-generated IDs: POST
  • Actions that shouldn't repeat: POST
PUT /users/123/status HTTP/1.1
{"status": "active"}

Idempotent. Setting status to "active" ten times leaves it active.

POST /users/123/notifications HTTP/1.1
{"message": "Welcome back!"}

Not idempotent. Sending twice sends two messages.

Testing Idempotency

Verify your idempotent methods are actually idempotent:

test('PUT /articles/:id is idempotent', async () => {
  const data = { title: 'Test', content: 'Content' };
  
  await request(app).put('/articles/123').send(data);
  await request(app).put('/articles/123').send(data);
  await request(app).put('/articles/123').send(data);
  
  const count = await countArticles({ title: 'Test' });
  expect(count).toBe(1);
});

Common Mistakes

GET with side effects: Don't modify state in GET handlers. Proxies and browsers will retry GET requests without asking.

Retrying POST blindly: Without idempotency keys or tokens, retries create duplicates.

Assuming PATCH is idempotent: It depends on the patch format. Increment operations aren't idempotent.

Frequently Asked Questions About HTTP Method Idempotency

Was this page helpful?

😔
🤨
😃
HTTP Method Idempotency • Library • Connected