All posts
ArticleCareer

The Stripe System Design Question That Separates Senior From Staff Engineers

Exactly-once payments under retries: idempotency keys, in-progress state machines, CAS, and when sagas enter the conversation—senior vs staff depth.

ossium
ossium4 min read

Payment systems sit on banks, card networks, merchants, and internal services. A network blip triggers a client retry. Did the first charge succeed? You may not know. Double charge, double fulfill, fraud flags—all possible.

How do you guarantee a payment executes exactly once when nothing in the network is guaranteed?

How you answer reveals depth: happy-path architecture vs failure-surface reasoning.

The deceptively simple question

Happy path: user clicks Pay → call payments API → store result.

Interviewers care about the gap between sending a request and knowing the result. Distributed systems live there.

Schemas, service boundaries, and rate limits matter—but the core is delivery semantics:

GuaranteeRisk
At most onceLost operations
At least onceDuplicate side effects
Exactly onceMust be built at the application layer

A retry is indistinguishable from a new request unless you make them distinguishable.

The senior answer: idempotency keys

Client generates a unique ID per intended operation and sends it with every attempt. Server stores the key and result. Same key again → return stored result; do not re-run the charge.

You’re trading storage for safety. Each intended operation gets a fingerprint that survives retries.

Stripe’s public docs describe this: clients send an Idempotency-Key header; the server deduplicates. A senior should explain:

  • How to store and expire records
  • Happy-path end-to-end flow
  • Why this stops the simple double-charge case

Stopping here is the usual senior ceiling—and the door to the staff follow-up.

The staff answer: state between key and result

Scenario:

  1. Fresh idempotency key arrives
  2. Card is charged
  3. Server crashes before writing “success”
  4. Client retries with the same key
  5. No stored result → charge again

The original bug reappears inside the “solution.”

Treat the idempotency record as a state machine, not a dumb cache:

StateOn retry
MissingStart fresh; write in progress before side effects
In progressConflict / wait—do not re-execute
CompletedReturn cached result

Concurrent retries need compare-and-swap or a lock so only one holder of “in progress” exists. That adds latency and makes the idempotency store a critical-path dependency. The trade-off—latency and availability for correctness—is what the question is really probing.

The hidden dimension: multi-service side effects

Your payment service may call several downstreams. One fails mid-flight: bank notified, inventory not updated, email not sent. Top-level idempotency does not automatically coordinate every child effect.

That boundary is distributed transaction design—where sagas live: local steps plus compensating actions, eventual consistency, not 2PC across five services.

Strong candidates reach keys + state machines. Exceptional candidates name coordination patterns (saga, outbox, explicit compensations) and their costs.

Mental model

Operation shapeYou need
No side effectsIdempotency free
Side effects, single serviceIdempotency keys + state machine
Side effects across servicesPlus saga / outbox / compensations

Portable line for any design discussion:

Exactly-once semantics in a distributed system require idempotency at the application layer, not the transport layer.

What interviewers are measuring

Not whether you’ve heard the word “idempotency.” Whether you:

  • Find where your answer still breaks
  • Explain trade-offs of the fix
  • Go one level deeper than the textbook key

Payments make missing guarantees expensive. The question is less about Stripe and more about clear thinking when nothing is guaranteed and everything has a cost.

The next hard problem—making compensating transactions themselves idempotent so recovery is safe to retry—is where much of the interesting systems work continues.

system-designdistributed-systemsidempotencyinterviewspayments

Related posts