# Idempotency vs Nonce: Two Weapons Against Duplicate Requests

Every backend developer eventually faces this scenario: a user clicks "Pay" twice, a network timeout causes a client to retry, or a message queue delivers the same event more than once. These situations can create duplicate orders, double charges, or corrupted data.

Two battle-tested strategies exist to defend against this:

**Idempotency** and **Nonce**. They look similar on the surface, but they serve different purposes. Let's break them down.

## The Problem: Why Do Duplicates Happen?

Before diving into solutions, let's understand the enemy.

```plaintext
Client  ──── POST /pay ────►  Server
Client  ◄──── timeout ──────  Server (processing...)
Client  ──── POST /pay ────►  Server  ← 💣 DUPLICATE!
```

The client never got a response, so it retried — but the first request was already processed. Now you have two charges.

This happens in:

*   Mobile apps on unstable connections
    
*   Message queues (at-least-once delivery)
    
*   Retry logic in service-to-service communication
    
*   Frontend double-clicks
    

## Solution 1: Idempotency

### What Is It?

An operation is **idempotent** if calling it once or calling it many times produces the same result.

> f(f(x)) = f(x)

Think of a light switch that only turns on: pressing it once or ten times — the light is on. Same result.

In HTTP terms: `GET`, `PUT`, and `DELETE` are naturally idempotent. `POST` is **not** — that's where you need to enforce it yourself.

### How It Works

The client generates a unique **Idempotency Key** and sends it with every request. The server stores the result of the first execution. On any subsequent request with the same key, the server returns the **cached result** without re-executing the operation.

```plaintext
Request 1: POST /pay  {idempotency-key: "abc-123"}  → Processed ✅  (result saved)
Request 2: POST /pay  {idempotency-key: "abc-123"}  → Returned from cache ✅  (not re-executed)
Request 3: POST /pay  {idempotency-key: "abc-123"}  → Returned from cache ✅  (not re-executed)
```

### Important Notes

*   The key should be generated by the **client** (usually a UUID v4).
    
*   Store idempotency records in a **persistent store** (DB or Redis), not in memory.
    
*   Set a **TTL** on records (e.g., 24 hours or 7 days) — you don't need them forever.
    
*   Handle **concurrent requests** with the same key using a database unique constraint or distributed lock.
    

## Solution 2: Nonce

### What Is It?

**Nonce** stands for **"Number used Once"**. It's a unique token that is valid for **a single use only**. Once consumed, it's permanently invalidated — even if the operation behind it failed.

The key difference from idempotency: a nonce doesn't cache results. It just asks: *"Has this token been used before?"*

### How It Works

```plaintext
Request 1: POST /transfer  {nonce: "xyz-789"}  → Valid, consumed ✅
Request 2: POST /transfer  {nonce: "xyz-789"}  → REJECTED ❌ (already used)
```

Even if request 1 failed for some business reason, request 2 with the same nonce is still rejected. The client must generate a **new nonce** for a new attempt.

### Important Notes

*   You can use **Redis** `SETNX` (SET if Not eXists) for atomic check-and-consume. Never do a separate GET then SET — that's a race condition.
    
*   Set a reasonable **TTL** based on your use case (5–15 minutes for form submissions, longer for payment flows).
    
*   The client must generate a **new nonce** for every new attempt. Nonces are not reusable.
    
*   Nonces work great for **one-time form submissions**, **OTP verification**, and **CSRF protection**.
    

## Side-by-Side Comparison

|  | **Idempotency** | **Nonce** |
| --- | --- | --- |
| **Core question** | "Has this operation been done before?" | "Has this token been used before?" |
| **On duplicate request** | Returns cached result | Rejects with error |
| **Client retry behavior** | Safe to retry with same key | Must generate a new nonce |
| **Stores** | Full request result | Just the token |
| **Best for** | Payment APIs, order creation, idempotent POST endpoints | Form submissions, OTPs, CSRF, single-use links |
| **Response on duplicate** | `200 OK` (cached) | `409 Conflict` |
| **Key generated by** | Client | Client (or server pre-issues it) |
| **TTL** | Hours to days | Minutes |

## When to Use Which?

```plaintext
Is the client allowed to retry with the same intent?
    │
    ├── YES → Use Idempotency
    │         (same operation, same result expected)
    │         Example: "Charge $100 for order #456"
    │
    └── NO  → Use Nonce
              (one shot only, new attempt = new token)
              Example: "Submit this form", "Verify this OTP"
```

**Real-world rule of thumb:**

*   Payment APIs → **Idempotency** (Stripe, PayPal both use this)
    
*   Authentication flows, form submissions → **Nonce**
    
*   CSRF protection → **Nonce**
    
*   Webhook delivery → **Idempotency**
    

## Combining Both

In high-security systems, you can use both together. This gives you the best of both worlds: replay protection in the short term (nonce) and safe retries over time (idempotency).

## Summary

| Concept | One-liner |
| --- | --- |
| **Idempotency** | Same key → same result, always safe to retry |
| **Nonce** | Single-use token → rejected on second use |

Both patterns are essential tools in any production backend. Idempotency is your friend when you need fault-tolerant retry logic. Nonce is your guard against replay attacks and accidental double submissions.

Once you start thinking in these terms, you'll spot the need for them everywhere.
