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 appendingInstalling 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.confRuns 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:
| Method | Idempotent? |
|---|---|
| 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:
- Read current state (from cloud APIs, saved state file)
- Compare to declared state
- Compute the diff
- 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
- Counters.
x = x + 1is not idempotent. If your code path “increments a counter when X happens” and can be re-triggered, you get drift. - Append instead of set.
>>in shell,list.append()in a state file,-A INPUT -p tcp --dport 22 -j ACCEPTin iptables (every run adds another rule). - Time-based logic. “Do X at 3 AM” triggered twice still does X twice unless you check “was X done today?”
- 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:
- If I run this twice in a row, what happens? It should be “nothing new.”
- If it fails halfway and I re-run, what happens? It should be “pick up where it left off.”
- 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.