Idempotency Is Not the Same as Concurrency Control

I am a developer from Lagos, Nigeria who loves wealth creation and good Music. When I am not coding or thinking up ways to move forward I DANCE
Idempotency and concurrency control are distinct backend design guarantees that solve two fundamentally different failure modes in distributed systems.
Idempotency ensures that executing the same logical operation multiple times does not produce unintended additional effects beyond the first successful execution. It provides retry safety.
Concurrency control ensures that multiple operations occurring simultaneously on shared data do not create race conditions or violate application invariants. It provides race-condition safety.
Although they are often used together, they solve different problems.
1. Core Definitions & Objectives
Idempotency: Preventing Duplicate Execution
Idempotency is about operation identity; concurrency control is about overlapping access to shared state.
Idempotency guarantees that an operation can be applied repeatedly without producing additional unintended state changes beyond the initial application.
Primary Objective: Protect the system against network timeouts, retries, double-clicks, duplicated queue messages, and other situations where the same logical operation may be submitted more than once.
Core Concern:
"If I send this identical request N times because I didn't get a response, will the system charge me N times or only once?"
Primary Mechanisms:
- Client-provided idempotency keys, for example:
X-Idempotency-Key: req_123abc
Storing idempotency keys and their results in a database or cache.
Unique constraints on database tables.
HTTP methods whose defined semantics are idempotent, such as
GET,PUT, andDELETE.
Concurrency Control: Preventing Conflicting Execution
Concurrency control is about overlapping operations on shared state.
It manages how multiple concurrent threads, processes, services, or users read and modify the same resources.
Primary Objective: Coordinate overlapping operations on shared state to prevent concurrency anomalies and preserve application invariants.
Core Concern:
"If User A and User B both try to purchase the last available ticket at the same time, will the system sell the ticket twice?"
Primary Mechanisms:
- Pessimistic Locking
SELECT ... FOR UPDATE;
Optimistic Locking
Version columns
Compare-And-Swap (CAS)
Distributed Locks
Redis-based locks
ZooKeeper leases
etcd leases
Database Transaction Isolation Levels
SERIALIZABLEREPEATABLE READand other isolation strategies depending on the required guarantees
2. Practical Scenarios & Comparisons
| Dimension | Idempotency | Concurrency Control |
|---|---|---|
| Problem Domain | Repeated execution of the same logical operation | Overlapping execution on shared state |
| Actor Model | Often the same actor retrying an operation | Different actors, or the same actor issuing parallel operations |
| Failure Mode | Duplicate effects such as double billing or duplicate creation | Race conditions such as overselling, lost updates, or corrupted totals |
| Scope | Logical operation identity across attempts | Shared-resource access across concurrent operations |
| Typical Identifier | Idempotency key / request identity | State version, row lock, resource lock, or atomic condition |
Scenario A: Idempotency Without Concurrency Control
A user submits a payment for $100.
The server successfully charges the card and saves the transaction, but the HTTP connection drops before the 200 OK response reaches the user's browser.
Attempt 1
The client sends:
Idempotency-Key: pay_888
The payment is successfully processed, but the network connection drops before the client receives the response.
Attempt 2
Five seconds later, the client retries the same operation using:
Idempotency-Key: pay_888
The server checks pay_888, discovers that the operation was already completed, skips the payment processing, and returns the previously stored result.
Concurrency control was not the primary concern in this scenario because there was no overlapping execution. The problem was a sequential retry of the same logical operation.
That is an idempotency problem.
Scenario B: Concurrency Control Without Idempotency
Suppose there is only one product remaining in stock.
Two different users, User X and User Y, click Buy Now at approximately the same time.
Without proper concurrency control, something like this can happen:
User X (Req A): READ stock (1) ------> Accept purchase ------> UPDATE stock = 0
User Y (Req B): READ stock (1) ------> Accept purchase ------> UPDATE stock = 0
Both requests observed:
stock = 1
Both therefore concluded that the product was available.
The database may ultimately contain:
stock = 0
but two purchases were accepted for a single available product.
The problem is not necessarily that the stored quantity becomes -1. The problem is that the business invariant:
accepted purchases <= available inventory
has been violated.
This is a race condition.
Fix With Concurrency Control
One solution is an atomic conditional update:
UPDATE inventory
SET quantity = quantity - 1
WHERE product_id = 42
AND quantity >= 1;
The application then verifies that exactly one row was updated.
If the first request reduces the quantity from 1 to 0, the second request's condition:
quantity >= 1
is no longer satisfied.
The second request therefore fails to reserve the product.
Another solution could use pessimistic locking:
SELECT quantity
FROM inventory
WHERE product_id = 42
FOR UPDATE;
The important point is that the shared inventory state must be protected against conflicting concurrent operations.
This scenario involves two distinct requests competing for the same resource. Neither request necessarily needs an idempotency key to solve that particular race condition.
3. The Critical Intersection: Concurrent Duplicate Requests
This is where idempotency and concurrency control intersect.
Suppose a user double-clicks a payment button.
Two identical requests containing the same idempotency key arrive almost simultaneously:
Idempotency-Key: pay_888
A naive implementation might perform:
Thread 1:
Check 'pay_888'
→ Not found
→ Process payment
→ Save 'pay_888'
Thread 2:
Check 'pay_888'
→ Not found
→ Process payment
→ Save 'pay_888'
Both threads can perform the lookup before either thread stores the key.
That creates a classic check-then-act race:
Thread 1 ──> CHECK ──> NOT FOUND ──> PROCESS PAYMENT
Thread 2 ──> CHECK ──> NOT FOUND ──> PROCESS PAYMENT
The system technically has an idempotency mechanism, but the mechanism itself is not concurrency-safe.
As a result, the payment may still be processed twice.
4. Combining Idempotency and Concurrency Control
To make the idempotency mechanism safe against concurrent duplicate requests, the system must make claiming an idempotency key atomic.
There are several ways to accomplish this.
Option 1: Database Unique Constraint
Create an idempotency record with a unique constraint on the key.
Conceptually:
CREATE TABLE idempotency_records (
idempotency_key VARCHAR(255) UNIQUE NOT NULL,
status VARCHAR(50) NOT NULL,
response TEXT
);
When the request arrives, the application attempts to claim the key:
INSERT idempotency_key = pay_888
Suppose two requests attempt this concurrently.
Thread 1 → INSERT pay_888 → succeeds
Thread 2 → INSERT pay_888 → conflicts
The database's unique constraint ensures that only one record with:
pay_888
can exist.
Depending on the database and transaction structure, the second transaction may block until the first transaction resolves and then receive a uniqueness violation.
The application can then determine whether the operation is:
IN_PROGRESS
or:
COMPLETED
and either wait, retry, or return the previously stored response.
The important point is that the database performs the claim atomically.
Option 2: Distributed Mutex
Another approach is to acquire a distributed lock for the idempotency key.
For example:
Lock(
key = "idempotency:pay_888",
ttl = 10s
)
The first request acquires the lock and processes the operation.
A concurrent request using the same key cannot process the operation simultaneously.
Conceptually:
Incoming Request
Idempotency Key: K
│
▼
Acquire Lock / Atomic DB Claim
│
├───────────────┐
│ │
Acquired Not Acquired
│ │
▼ ▼
Check key Wait / Retry
│
┌────┴────┐
│ │
Exists Missing
│ │
▼ ▼
Return Process
stored business
result operation
│
▼
Save result
│
▼
Mark COMPLETE
│
▼
Release lock
The idempotency key identifies the logical operation.
The concurrency-control mechanism prevents multiple workers from claiming and executing that operation simultaneously.
5. The External Side-Effect Problem
There is another important complication in distributed systems.
Consider this sequence:
1. Create idempotency record
2. Charge payment provider
3. Save successful result
4. Mark idempotency record COMPLETE
What happens if the application crashes between steps 2 and 3?
Create idempotency record
│
▼
Charge payment provider
│
▼
CRASH
X
│
▼
Result never recorded
The external payment may have succeeded, but the local application does not know whether it succeeded.
When the request is retried, blindly processing the payment again could result in another charge.
A local idempotency table alone cannot make an external side effect atomic with the application's database transaction.
This is why payment systems and other distributed workflows often combine techniques such as:
Downstream provider idempotency keys
Durable operation states such as
PENDING,PROCESSING,COMPLETED, andFAILEDReconciliation processes
Transactional messaging or outbox patterns where appropriate
The exact solution depends on where the side effect occurs and which system owns the authoritative state.
6. Summary Architectural Takeaway
The easiest way to distinguish these concepts is by the question each mechanism answers.
Idempotency asks:
"Have I already performed this logical operation?"
Its job is to prevent the same logical operation from producing duplicate effects when it is submitted multiple times.
Concurrency Control asks:
"Can these operations safely access or modify this shared state at the same time?"
Its job is to prevent overlapping operations from violating application invariants.
Their intersection asks:
"What happens when the same logical operation is executed concurrently?"
That is where an idempotency mechanism itself must be concurrency-safe.
In short:
Idempotency guarantees correctness under repeated execution by giving a logical operation a stable identity.
Concurrency control guarantees correctness under overlapping execution by controlling access to shared mutable state.
Operations exposed to both retries and concurrent access to shared state may require both mechanisms working together.



