DEV Community

Mohamed Idris
Mohamed Idris

Posted on

"Idempotent": The Scary Word That Means "Press It Again, It's Fine"

You click Pay Now. The page hangs. Did it work? You don't know. So you click
it again. And now you're sweating, because what if you just paid twice?

That little moment of fear is exactly the problem one fancy word solves. The word
is idempotent. It sounds like a spell from a wizard book, but the idea is
simple and you already understand it from real life.


The idea in one line

An action is idempotent if doing it once or doing it ten times leaves the
world in the same state.


The metaphor: a light switch vs a counter

Think about two buttons.

ELEVATOR "UP" BUTTON              A TALLY COUNTER
press once  -> light on          press once  -> 1
press again -> still on          press again -> 2
press 50x   -> still on          press 50x   -> 50
   = IDEMPOTENT                      = NOT idempotent
Enter fullscreen mode Exit fullscreen mode

Pressing the elevator button again does nothing new. The button is already lit,
the elevator is already coming. That extra press is harmless. That is
idempotent.

The tally counter is the opposite. Every press changes the result. Pressing it
again is never safe if you only meant to count once.

So when someone says "make this endpoint idempotent," they mean: make it like
the elevator button, not the tally counter.


Where you actually meet this: HTTP

HTTP methods are a classic example. Some are idempotent by design.

GET    /users/7      read it        -> idempotent (reading never changes anything)
PUT    /users/7      set name="Al"  -> idempotent (set to the same value = same result)
DELETE /users/7      remove user 7  -> idempotent (gone once, gone forever)
POST   /users        create a user  -> NOT idempotent (each call makes a NEW user)
Enter fullscreen mode Exit fullscreen mode

See PUT? "Set the name to Al" gives the same result whether you call it once or
five times. The name is just Al at the end. But POST to create a user? Call it
five times and you get five users. Oops.

That's why a flaky network plus a POST is the scary "did I pay twice?" combo.


The fix: an idempotency key

Real payment APIs (like Stripe) solve this with an idempotency key. You make
up a unique id for the action and send it along:

// What a junior might write first
await fetch("/charge", {
  method: "POST",
  body: JSON.stringify({ amount: 2000 }),
})
// retry on timeout -> might charge twice. yikes.

// The fix: send a key that names THIS one attempt
await fetch("/charge", {
  method: "POST",
  headers: { "Idempotency-Key": "order-42-attempt" }, // same key on every retry
  body: JSON.stringify({ amount: 2000 }),
})
Enter fullscreen mode Exit fullscreen mode

Now the server has a rule: "If I already saw order-42-attempt, don't charge
again. Just return the result from last time." Retry as much as you want. One
charge. The button is now an elevator button.


A real case you've probably hit

Webhooks. A service like GitHub or Stripe sends your server a message ("payment
succeeded!"). Networks being networks, they sometimes send the same message
twice to be safe. If your handler is idempotent, the duplicate does no harm. If
it isn't, you might email the customer twice or ship two boxes.

The senior move: check "have I already handled event evt_123?" before doing
the work.


Gotchas juniors hit

1. Thinking idempotent means "returns the same response."
Not quite. It means the state ends up the same. A second DELETE might
return 404 Not Found instead of 200 OK, and that's fine. The user is still
deleted. Same world.

2. Assuming POST can never be safe.
It can, with an idempotency key. The method isn't magic; your design is.

3. Forgetting reads can have side effects.
A GET that secretly bumps a "view count" is no longer truly idempotent. Keep
reads clean.


Recap

  • Idempotent = doing it again changes nothing new. Like an elevator button.
  • Not idempotent = each call adds up. Like a tally counter, or POST /users.
  • It's about the final state, not the exact response text.
  • Make risky actions safe with an idempotency key so retries can't double up.

Your turn

Look at the last form you built. If a user double-clicks submit on a slow
connection, what happens? If the answer is "two records," you found a spot that
wants idempotency. How would you fix it?

If you can explain "elevator button vs tally counter" to a friend, you've got it.

Top comments (0)