What happens when a function call fails? Can you retry without causing any unintended side effects? If the answer is no, then your function is probably not robust enough.

Here is a scenario that is very likely happening to someone somewhere right at this moment. Let’s call this person Midori. Midori has been eyeing a pair of headphones, fancy noise cancelling ones, no less, and as soon as there’s a sale on them, she adds the phones to her shopping cart, and with a touch of excitement, clicks the Order button. A few seconds pass, but nothing happens, and then she waits some more, but still nothing. Has the order gone through? Does she need to order again?

There can be different reasons as to why the order page might get stuck – Midori’s Internet connection might have dropped right before or after she clicked the Order button, or maybe her connection was OK, but the server was busy.

Midori goes back to her cart and notices the headphones are still there. Can she safely assume that the order hasn’t gone through? Maybe the cart hasn’t been updated to reflect the order, and is showing outdated information.

A function is called “idempotent” if multiple calls to the function have the same effect on the program state as a single call. In other words, no matter how many times you call the function with the same arguments, the program state will not change after the first call.

In Midori’s case, if the server has received the order, but has no way to respond due to the dropped connection, a second order won’t be executed if the function responsible for the ordering process was designed to be idempotent. For example, if the shopping web site sends a unique “idempotency” key to the server for each order, the server will realize that it has already executed the order, and won’t do anything.

Idempotency is often discussed in the context of HTTP methods. According to the HTTP 1.1 specification, in addition to the normally idempotent GET method, the methods PUT (update), and DELETE should be implemented in an idempotent manner, but this is only possible if unique identifiers are used. For example, the function handling the following request

PUT /messages/id/123
{"status": "read"}

can be made idempotent because only the message with the id 123 will be updated. However, if your API supports updating messages using non-unique identifiers, such as a date range, it may not be truly idempotent because the same date range can update different messages in the future.

Resuming Interrupted Operations

Idempotent functions aren’t just about limiting state changes – they may also allow resuming interrupted operations. For example, if you are synchronizing files across computers, and the process is interrupted, a second call can start where the previous call left off. Long-running batch SQL jobs that process millions of records are also prime candidates for idempotency. If something goes wrong, they won’t have to start from the beginning.

Pure Functions

So far, we’ve talked only about functions that change the program state, but idempotency is also a natural property of “pure” functions. Pure functions always return the same results for a given set of arguments. They don’t modify the passed arguments or depend on external components like non-local variables, databases, or network connections that could change outside the function’s control.

function getFullName(firstName, lastName) {
	return firstName + ' ' + lastName;
}

is a pure function, but the following isn’t because it refers to non-local variables that can be changed by other functions at any time, and yet it’s still idempotent because it doesn’t change the program state at all.

var firstName, lastName;

function getFullName() {
	return firstName + ' ' + lastName;
}

Utilizing idempotent functions, both pure and impure, not only makes your code more robust by minimizing unintended side effects but also makes it easier to reason about your code.


Related: