Home

Quasi-idempotent operations

Note: article for programmers

When dealing code that has side effects you need to consider the consequences of running your code twice with similar input parameters (i.e. lets call these quasi-idempotent operations). I had a piece of code where I did not consider the consequences and I had a resulting bug.

Example illustrating the bug:

Take this piece of code with a side effect (e.g., updating database):

      await ConfirmationRequest
        .update({
          event: event.id,
          user: user.id
        }, {
          status: is_accept_msg(body)
            ? 'accepted'
            : is_reject_msg(body)
              ? 'rejected'
              : 'unconfirmed'
        })

Assume this code is called twice:

  • First call: with body so that is_accept_msg(body) = true
  • Second call: with body so that is_accept_msg(body) = false

Both calls with the same user and event.

After the first call the status is "accepted". After the second call the status is overwritten to "rejected" or "unconfirmed".

Assume your business logic is that once you accepted you cannot reject the confirmation request. Then you have a bug.

If you're writing functional code most of the time you never think about this issue because functional code is always idempotent (no side effects = idempotent).