Idempotency
The “Do It Again, Nothing Changes” Principle
The Core Idea
Idempotency means: performing an operation multiple times produces the same result as doing it once.
The word comes from Latin: idem (same) + potent (power). Same power, no matter how many times you apply it.
The mental hook: Think of a light switch vs. a light toggle. Pressing a dedicated “ON” button ten times still just leaves the light on — that’s idempotent. Pressing a toggle ten times leaves you with an unpredictable state — that’s not.
The Everyday Analogy
Setting your thermostat to 72° F is idempotent. You can “set it to 72° F” five times in a row, and you always end up at 72° F. Compare that to “raise the temperature by 2° F”. Do that five times, and you’re at 82° F. One is safe to repeat. The other is dangerous to repeat.
Python Examples
NOT Idempotent - Appending to a list
results = []
def add_user(user):
results.append(user) # Each call changes state
add_user("sean")
add_user("sean")
add_user("sean")
print(results) # ["sean", "sean", "sean"] - different every time
Idempotent - Using a set, or checking first
results = set()
def add_user(user):
results.add(user) # Repeating has no extra effect
add_user("sean")
add_user("sean")
add_user("sean")
print(results) # {"sean"} - always the same
Idempotent - File Writing
# NOT idempotent - appends grow the file every run
with open("log.txt", "a") as f:
f.write("Server started\n")
# IDEMPOTENT - overwrite always produces the same file
with open("config.txt", "w") as f:
f.write("debug=true\n")
Idempotent - Database Upsert Pattern
# NOT idempotent - creates duplicate rows
def create_user(db, email):
db.execute("INSERT INTO users (email) VALUES (?)", (email,))
# IDEMPOTENT - safe to call repeatedly
def upsert_user(db, email):
db.execute("""
INSERT INTO users (email) VALUES (?)
ON CONFLICT(email) DO NOTHING
""", (email,))
Idempotent - Using dict.update() carefully
config = {"theme": "dark", "lang": "en"}
# Idempotent - applying the same patch always yields the same result
def apply_defaults(cfg):
cfg.setdefault("lang", "en")
cfg.setdefault("timeout", 30)
apply_defaults(config)
apply_defaults(config) # Safe - no double-setting
PHP Examples
NOT Idempotent — Incrementing a counter naively
function recordVisit($userId) {
$db->query("UPDATE users SET visits = visits + 1 WHERE id = $userId");
// Calling this twice double-counts!
}
Idempotent — Using a unique event log
function recordVisit($userId, $sessionId) {
$db->query("
INSERT IGNORE INTO visits (user_id, session_id)
VALUES (?, ?)
", [$userId, $sessionId]);
// Same session_id = no duplicate row
}
Idempotent — array_unique style deduplication
// NOT idempotent
$tags[] = "php"; // Adds every time
// IDEMPOTENT
$tags = array_unique([...$tags, "php"]); // Always the same result
Idempotent — File/Directory Creation
// NOT idempotent — throws if dir already exists
mkdir("/var/app/cache");
// IDEMPOTENT — safe to run in setup scripts every deploy
if (!is_dir("/var/app/cache")) {
mkdir("/var/app/cache", 0755, true);
}
// Or simply:
@mkdir("/var/app/cache", 0755, true); // suppress "already exists" error
Idempotent — HTTP Verbs (Critical for APIs)
// PUT is idempotent — replaces the whole resource
// Calling this 10 times = same end state
$app->put('/users/{id}', function($id, $data) {
User::updateOrCreate(['id' => $id], $data);
});
// POST is NOT idempotent — creates a new resource each time
$app->post('/users', function($data) {
User::create($data); // 10 calls = 10 new users
});
The “Why Should I Care?” Summary
Here are the real-world scenarios where idempotency saves you:
• Retry logic / network failures: If a request fails mid-flight, can you safely retry it?
• Database migrations: Can you run your migration script twice without breaking things?
• Deployment scripts: Can you re-run setup without duplicating data or crashing?
• Message queues: If a message is delivered twice, does your handler blow up?
• REST API design: Is your PUT truly replacing, or accidentally appending?
The One-Sentence Rule to Remember
“If calling this function twice in a row is safe, it’s idempotent. If it’s dangerous, it’s not.”
Whenever you write a function that touches state, a database, a file, a list, ask yourself: what happens if this runs twice? That question alone will save you from a whole category of subtle, hard-to-debug bugs.
Claude
In this new age of AI, I think it's important to clearly call out when something is largely AI-generated. Big shout-out to Claude by Anthropic for writing this great explanation of idempotency and for having ethics around AI development.