What’s idempotency?
First introduced in 1870 by American mathematician Benjamin Peirce, the term idempotence means “the quality of having the same power” (idem + potence = same + power).
In math, a function is considered to be idempotent if it can be applied multiple times without changing the result beyond the initial application.
f(f(x)) = f(x)
For example, the absolute value is an idempotent function:
abs(abs(-3)) = abs(-3) = 3
Idempotency in software
In computer science, functions can be labeled as pure and non-pure. The meaning of idempotent will change depending on the type of function we are talking about.
Pure functions have the following properties:
i) Always return the same output when they are given the same arguments.
ii) Have no side effects. They don’t change the state of the program.
A pure function is idempotent if it’s idempotent in the mathematical sense.
f(f(a)) == f(a) // Evaluates to true if f() is idempotent.
A non-pure function is idempotent if multiple calls with same arguments, leave the system in the same state as a single call.
// Program is in state A.
f(a) // First call to f() changes the state to B.
f(a) // Second call does not change the state because f() is idempotent.
// Program is in state B.
f(b) // A call to f() with different arguments can still change the state in spite of f() being idempotent.
// Program is in state C.
IETF Definition
The Internet Engineering Task Force uses the following interpretation of idempotence when referring to HTTP methods.
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.
Internet Standard – RFC 9110
Please note the usage of the word “intended”. The IETF uses a more relaxed definition of what they consider as idempotent. As we’ll see in the next section, this definition makes more sense than the formal ones for real world applications.
Why is idempotency important?
In microservice architectures, different services communicate between them over network. And networks are unreliable.
Let’s take a look at the following example, where a client tries to make a deduction from a banking account that exposes an HTTP interface:
The client intends to execute a single deduction, but does not receive a response after the initial request. The client assumes that not receiving a response means that the request was not processed, and decides to try again. After getting a successful response from the second request the client may assume that only one deduction was executed, but in reality two deductions are made.
The client mistake was assuming that not receiving a response meant that the original deduction was not processed. If two request were made but only one response was received, the only safe assumption to make is that the deduction was processed at least once.
When making requests that change the state of a service, you can only make a limited set of assumptions based on the response received.
Response | Request processed? | Observations |
---|---|---|
2xx | Yes | The requests was processed successfully. |
4xx | No | The request is correctly processed without success. The response indicates a client-side error. |
5xx | Don’t know | Server-side error. It’s unclear whether the error happens before or after processing the request. The server could have saved changes to its DB, encountering a problem just before sending the response. |
Timeout / Response not received | Don’t know | Network error, server-side error or the client did not wait long enough to receive a response. Same as the 5xx scenario, no assumption can be made. |
As it is, the banking service from the example does not provide a clear path for error handling to its clients. Upon timing out or receiving a 5xx response, a client can:
- Give up without knowing whether the deduction was processed or not.
- Try again, but running into the possibility of making more than one deduction.
From the client perspective it would nice to know that retries won’t trigger duplicate deductions. This falls right in line with the IETF definition of idempotency. The client needs a single request to have the same intended effect as multiple identical ones. The banking service could provide this guarantee by implementing the deduct endpoint as an idempotent operation. With such implementation, the server would process every request (and its retries) at most once.
✍️ Key takeaways
- Idempotent endpoints prevent identical requests from being processed more than once.
- Clients of idempotent endpoints can safely retry requests making them more resilient against server or network errors. If something goes wrong, a client can simply keep trying, eventually getting a correct response from the service.
- Idempotency is mandatory for systems where correctness and reliability are important. Digital banks, eCommerce portals, exchanges, digital wallets and trading platforms are just some examples of such systems. If you are yet to be convinced, just try to imagine how a user would feel when a transaction made from a banking app ends up deducting twice the expected amount.