Idempotence

An operation is idempotent if running it N times has the same effect as running it once. The word comes from mathematics (idem = same, potens = power), and it is the single property that separates reliable automation from “hope for the best” automation.

The definition, concretely

For an operation f, idempotence means:

f(x)    = f(f(x))    = f(f(f(x)))    = f(f(f(f(x))))    = ...

Apply it once, or a hundred times — the end state is the same.

Everyday examples

Idempotent

  • Pressing an elevator “up” button. One press, ten presses — elevator still just comes up.
  • mkdir -p /tmp/foo — creates the directory if missing, does nothing if it exists.
  • HTTP GET — fetching a URL multiple times returns the same thing; the server state isn’t changed.
  • HTTP PUT — “set this resource to this value” — applied twice, same outcome.

Not idempotent

  • Pressing an elevator floor button multiple times when already travelling. Extra presses don’t help.
  • mkdir /tmp/foo (no -p) — errors out the second time.
  • HTTP POST — “create a new thing” — twice creates two things.
  • useradd deploy — fails the second time because the user already exists.

Why automation cares

You run your automation repeatedly:

  • On schedule (every 30 minutes, or every hour)
  • On triggers (push to git, CI runs, webhook fires)
  • When something went wrong and you’re re-running to fix it

If the operation isn’t idempotent, every re-run is a gamble:

  • Will it duplicate something?
  • Will it error out in the middle?
  • Will it leave the system in a worse state than before?

Idempotence lets you re-run fearlessly. Ran twice by mistake? No harm. Partial failure halfway through? Re-run; the parts that succeeded stay done, the rest get retried.

Designing idempotent operations

The pattern, at every scale, is: check then change.

if desired_state != current_state:
    make_change()

Every idempotent operation boils down to that. Examples:

Creating a file

# Not idempotent — fails if file exists
touch /etc/myapp.conf          # OK, actually this IS idempotent
echo "key=value" > file        # idempotent for content
echo "key=value" >> file       # NOT idempotent — keeps appending

Installing a package (Ansible-style)

- name: nginx is installed
  apt:
    name: nginx
    state: present      # "make sure it is present"

Ansible checks if nginx is installed. If yes, does nothing. If no, installs it.

Writing config

Bad (not idempotent):

echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf

Runs N times, line appears N times.

Good (idempotent):

- name: ip_forward is enabled
  lineinfile:
    path: /etc/sysctl.conf
    regexp: '^net\.ipv4\.ip_forward='
    line: 'net.ipv4.ip_forward=1'

Runs N times, line appears exactly once.

Idempotence vs “doing nothing”

Idempotent ≠ does nothing. A run that detects and corrects drift is still idempotent:

  • Run 1: nothing installed → installs nginx
  • Run 2: nginx installed → does nothing
  • Run 3: someone uninstalled nginx → re-installs nginx (reaches the same desired state again)

The end state is consistent. That’s what matters.

Idempotence in different domains

REST APIs

HTTP methods and their idempotence:

MethodIdempotent?
GET
HEAD
PUT
DELETE
POST
PATCH✗ (usually)

A well-designed API often requires a client-supplied idempotency key for POST requests — so if the client retries, the server recognises the duplicate and doesn’t create two orders.

Infrastructure as Code

Terraform is entirely built on idempotence. Its plan/apply loop:

  1. Read current state (from cloud APIs, saved state file)
  2. Compare to declared state
  3. Compute the diff
  4. Apply only the diff

If nothing has drifted, apply does nothing. If something has, it converges.

Config management

Ansible’s central promise. Every well-written Ansible task should be idempotent by default — the state: present/absent pattern, not command: do-this-step.

Database migrations

Migrations are usually not idempotent. CREATE TABLE users (...) errors the second time. Good migration frameworks track which migrations have run and skip them.

CREATE TABLE IF NOT EXISTS makes the individual statement idempotent. But data migrations (UPDATE ... SET flag = true WHERE ...) are harder — typically wrap in a transaction + use versioned migrations.

Kubernetes

kubectl apply -f deployment.yaml is idempotent. Apply ten times — still three replicas. The declared desired state is the source of truth.

kubectl create -f deployment.yaml is not idempotent — errors if the object exists.

Common idempotence failures

  1. Counters. x = x + 1 is not idempotent. If your code path “increments a counter when X happens” and can be re-triggered, you get drift.
  2. Append instead of set. >> in shell, list.append() in a state file, -A INPUT -p tcp --dport 22 -j ACCEPT in iptables (every run adds another rule).
  3. Time-based logic. “Do X at 3 AM” triggered twice still does X twice unless you check “was X done today?”
  4. External side effects. “Send an email when config applies” — runs twice, sends two emails. Idempotent at the config level, non-idempotent at the effect level.

The test

For any automation you write, ask:

  1. If I run this twice in a row, what happens? It should be “nothing new.”
  2. If it fails halfway and I re-run, what happens? It should be “pick up where it left off.”
  3. If someone manually changes state between runs, what happens? It should be “fix the drift back to declared state.”

If any of these three is wrong, your automation is fragile.

See also