−Idempotency means that requesting the same operation repeatedly has the same effect as applying it once. If the response to an order request is lost, the caller cannot easily tell whether no order was created or whether the order exists and only the response went missing. Identifying the request and returning its stored result lets the caller recover the result without creating another order.
An identical request body does not necessarily mean the same operation: a user may want to order the same product twice. The example below therefore combines an authenticated user identifier with a request key chosen by the caller. Reusing that key with different content is an error. AWS on idempotent API design
[Image]
The diagram shows the sequence between create_order and db below. Both writes belong in one transaction so that an order cannot remain without its request record, or a request record without its order.
Creating one order and returning its result again
This standalone example uses Python 3.12 and SQLite from the standard library. owner identifies a user who has already passed authentication and authorization checks, and item is the entire request payload in this example. It uses one connection to an in-memory database, so the records disappear when the program ends.
import sqlite3
db = sqlite3.connect(":memory:", isolation_level=None)
db.executescript("""
CREATE TABLE orders(id INTEGER PRIMARY KEY, item TEXT NOT NULL);
CREATE TABLE requests(
owner TEXT NOT NULL, request_key TEXT NOT NULL,
item TEXT NOT NULL, order_id INTEGER NOT NULL,
PRIMARY KEY(owner, request_key)
);
""")
def create_order(owner, request_key, item):
db.execute("BEGIN IMMEDIATE")
try:
row = db.execute(
"SELECT item, order_id FROM requests WHERE owner=? AND request_key=?",
(owner, request_key),
).fetchone()
if row is not None:
if row[0] != item:
raise ValueError("KEY_REUSED_WITH_DIFFERENT_REQUEST")
order_id = row[1]
else:
order_id = db.execute(
"INSERT INTO orders(item) VALUES (?)", (item,)
).lastrowid
db.execute(
"INSERT INTO requests VALUES (?, ?, ?, ?)",
(owner, request_key, item, order_id),
)
db.commit()
return order_id
except Exception:
db.rollback()
raise
if __name__ == "__main__":
first = create_order("alice", "request-1", "book")
again = create_order("alice", "request-1", "book")
assert first == again
assert db.execute("SELECT COUNT(*) FROM orders").fetchone()[0] == 1
print(first, again) # 1 1
db.close()
The first call stores the order and request record; the next call reads the order_id for the same key. Sending pen with that key raises ValueError and rolls back the transaction. The composite primary key treats another user's identical key as a separate operation.
The except block cleans up the transaction and propagates the original exception. The code does not retry automatically or substitute a default result. BEGIN IMMEDIATE and the composite primary key enforce write ordering and request identity in this SQLite example. A service with multiple connections must still check for duplicates and record the effect in the same database transaction.
Retrying after a lost response
[Image]
(Original source)
Original diagram from Malcolm Featonby's Making retries safe with idempotent APIs, PDF page 4, printed page 3. The retry keeps the same identifier even when the first response never reaches the caller. The service recognizes the completed request and prepares a semantically equivalent response. Only the diagram was excerpted; its notation and arrows are unchanged. Original PDF, © Amazon Web Services.
Preventing duplicate effects and reusing responses are separate parts of the contract. A second call could return "already exists" while still applying the effect only once, but a caller trying to recover the first successful result needs to know which result it will receive. This example returns the stored order number.
Request key scope and retention
In production, decide which user and operation a key belongs to, how to determine whether request content is equivalent, how long records remain, and how to handle requests still in progress. Once a record is deleted, a late request can no longer be distinguished from a new one on that evidence alone. Design the valid retry window and retention policy together. The same key with a different payload must not be accepted as a successful duplicate. AWS on late requests and different intent
A database transaction does not also cover payments, email, or file writes outside that database. Use idempotency keys provided by the external service, or record an event for delivery with a Transactional outbox and design duplicate handling at the receiver. HTTP method idempotency likewise concerns the method's intended effect; response bodies and incidental logs may differ between calls. RFC 9110 §9.2.2
Related reading
Retry Pattern
Command Pattern
Optimistic concurrency control
Transactional outbox
+A button that sets the volume to 20 behaves differently from one that increases it by 1. Repeating the first command leaves the volume at 20; repeating the second keeps increasing it. Idempotence, often called idempotency in API design, is the property that applying an operation again preserves the result of applying it once.
Server developers often encounter the term through duplicate payments or request keys, but it comes from mathematics. Algebra concerns repeated operations, HTTP concerns the intended effect of requests, and business APIs add contracts for identifying and processing the same operation again. These uses are connected, though each requires us to specify what repeats and what counts as the same result.
A name from powers in algebra
Section 25 of Benjamin Peirce's Linear Associative Algebra uses the word idempotent and states that, unless otherwise specified, it will mean A² = A. The accompanying term nilpotent describes an expression whose power becomes zero. These names distinguish algebraic objects by what repeated multiplication produces. This is Benjamin Peirce, the father of Charles Sanders Peirce, who is also known for his work in philosophy.
[Image]
(Original source)
This crop is from printed page 8, PDF page 20, of the 1882 edition. Its preface says that lithographed copies circulated in 1870, so the earlier circulation and the edition shown here should be distinguished. The passage mentions higher powers before adopting the square as its default. The image reproduces a public-domain scan. Edition record, preface.
For composition of a function f from a set to itself, the corresponding condition is f(f(x)) = f(x) for every input x. Applying the function to its own result leaves that result unchanged. Absolute value has this property on integers: the first application has already removed the negative sign. Squaring takes 2 to 4 and then 16, so it is not idempotent.
def square(x):
return x * x
assert abs(abs(-3)) == abs(-3)
assert square(square(2)) != square(2)
print(abs(-3), square(2), square(square(2))) # 3 4 16
This runs in Python 3. Calling square(2) repeatedly and getting 4 each time shows determinism. The example instead applies square to its previous result. Keeping repeated calls with the same input separate from composition helps distinguish pure functions from idempotent ones.
The term also applies to binary operations, such as set union: A ∪ A = A. Here the law compares an operation whose two operands are equal, rather than function composition. The shared relationship is that repetition adds no further change to the result.
Programs apply operations to state
For a command, we often need to compare changes in state rather than return values. If s is a state and T is the command's state transformation, the condition is T(T(s)) = T(s). Setting the volume is an example.
[Image]
Both rows start at volume 5 and apply their respective command twice. This explanatory diagram compares state transformations.
def set_volume(state):
return {**state, "volume": 20}
def increase_volume(state):
return {**state, "volume": state["volume"] + 1}
initial = {"volume": 5}
assert set_volume(set_volume(initial)) == set_volume(initial)
assert increase_volume(increase_volume(initial)) != increase_volume(initial)
print(set_volume(initial), increase_volume(increase_volume(initial)))
The functions return new dictionaries to make the state transformations visible; they do not operate an audio device. Both are written as pure functions, but only one is idempotent. A command that writes a setting to a real device can have side effects while remaining idempotent with respect to the observed volume.
These properties answer different questions.
Property | What it compares | Example
Determinism | Does the same input produce the same result? | Integer squaring
Purity | Does the result depend only on the input, without observable side effects? | A function that computes a square
Idempotence | Does applying the operation again preserve the first result? | Absolute value; setting the volume to 20
Involution | Does applying it twice restore the original input? | Boolean negation
Negating a boolean twice restores its original value, which differs from the value after one negation. Also, an operation that happens to preserve a particular input need not be idempotent over its full domain: squaring leaves both 0 and 1 unchanged.
Communication failures give us a reason to repeat
Remote calls create a practical reason to send the same command again. If a server saves an order but its response disappears, the caller may be unable to distinguish an undelivered request from a completed request whose reply was lost. Retransmission helps recover from communication failure, but repeating an order-creation effect can create another order.
Birrell and Nelson's 1984 paper Implementing Remote Procedure Calls, section 3.2, describes call identifiers and sequence numbers for recognizing duplicate call packets. Retransmission and duplicate execution were already separate concerns in this RPC design. This is a historical example of duplicate suppression, rather than evidence that the paper invented idempotence. Publication record, paper PDF.
The 1997 HTTP/1.1 specification, RFC 2068, distinguishes safe methods from idempotent methods in section 9.1. Asking for retrieval and repeating a request without accumulating its effects are different properties. The distinction informs which requests a client can retry after a connection fails. RFC 2068 sections 8.1.4 and 9.1.
RFC 7231 in 2014 and RFC 9110 in 2022 retain this distinction. The table describes the semantics promised by the methods; using a method name still requires the server to implement that promise. RFC 7231 section 4.2.
Method | Safe? | Idempotent? | Reason
GET and HEAD | Yes | Yes | The request means retrieval
PUT | No | Yes | It specifies target resource state without accumulating repeated effects
DELETE | No | Yes | Repeating removal adds no further removal effect
POST | No | No general guarantee | The resource and application define the processing or creation semantics
A first DELETE may return 204 and a later one 404 while preserving the intended deletion effect. RFC 9110 permits per-request logging or revision history and explains that responses to idempotent requests may differ. Its comparison concerns the intended server effect. RFC 9110 section 9.2.2.
Why order creation needs a request identity
A command to add an order normally creates another order each time it runs. Such an API can identify a repeated attempt at the same operation and avoid creating its effect again.
[Image]
(Original source)
This is the original diagram on PDF page 4, printed page 3, of Malcolm Featonby's Making retries safe with idempotent APIs. It illustrates an API contract that keeps the same identifier after losing a response. It concerns request deduplication, one application of the broader concept. © Amazon Web Services.
The article favors an explicit caller-provided identifier over assuming that matching request bodies mean matching intentions. A customer may legitimately order the same item and quantity twice. Caller scope, reuse of a key with different content, and record retention all affect what the API recognizes as the same operation. AWS article.
There are two design choices here. An operation can specify the desired state directly, or it can remain an additive operation whose repeated requests are controlled by records. The latter requires the effect and its record to be handled atomically. A local database transaction alone cannot cover an external payment service's effect. Whether to reuse a response is another part of the API contract.
Idempotence is a property of repeated effects; deduplication is a technique for recognizing an operation already seen. Within its retained records and other stated assumptions, deduplication can help provide idempotent behavior. Expired records and effects in other systems require further guarantees. An “exactly once” claim likewise needs to identify whether it concerns delivery, execution, or a business effect.
What stays the same in each context
Context | What repeats | What is compared
Algebra | Multiplication of the same element | The element itself
Function composition | Applying a function to its previous result | The function value
Stateful programs | Applying the same command again under the stated conditions | The chosen observable state
HTTP | Retransmitting the same request | The intended server effect
APIs with request keys | Processing the same operation identifier again | Contracted business effects and response behavior
As this algebraic property is used in communication and business processing, the scope of equality and the mechanism that provides it become part of the explanation. Specifying the repeated operation and the observed effect makes the guarantee clear. Preventing duplicate orders is one application; request keys and stored results are chosen mechanisms for providing it.