~learn.
Code lab · World 02 · Failure-First Design

Idempotency-key store.

A retried payment returns the first response instead of charging twice, a reused key with a different body is refused, and failures can be retried.

Medium~35 min3 example + 10 hidden tests+90 XPJump to the code ↓
Saving also stores the Python runtime (~13.5 MB, once for every lab) so this lab runs with no internet.

The client sends Idempotency-Key: pay_abc123 with a payment. The network drops the response, the client retries with the same key, and the server must return the first response instead of charging again (Day 2, sections 2.1 to 2.4). Build the in-memory version of the lesson's IdempotencyStore, with the same record states: processing, completed, failed.

Implement IdempotencyStore(clock, ttl_seconds=86400):

Method Does
check_and_set(key, request_body) (True, None) for a new key, which it claims as processing. (False, response) for a completed key. Raises IdempotencyInProgressError while the key is processing, and IdempotencyConflictError when the key was used with a different body. A failed key is claimed again: (True, None)
complete(key, response) marks the key completed and stores the response. Raises ValueError for an unknown key
fail(key, error) marks the key failed so a retry can run again
handle(key, request_body, process) the lesson's with_idempotency: on a new key call process() once, store and return its response; on a completed key return the stored response without calling process; if process raises, mark the key failed and re-raise

Rules the tests check:

  • Compare request bodies with the given fingerprint(body), so {"a": 1, "b": 2} and {"b": 2, "a": 1} are the same request.
  • A record is forgotten ttl_seconds after it was created (Day 2, chapter 4): after that the key is new again. Read time only through clock.now().
  • Store a copy of the response. A caller that mutates the dict it got back must not change what later retries receive (Redis stores serialized JSON for the same reason).
Python 3.14 loads on first run (~6.5 MB once)14
Loading the editor…
not run yet · 8 s limit
press Run tests

Write your code, then run 3 example tests and 10 hidden tests. Each run is limited to 8 seconds: an infinite loop gets stopped and Python restarts, your code stays.