Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Low-Level Design (Object-Oriented Design)  Module 5 – Modelling lifecycles, time, money and rules

IDs, idempotency keys and deduplication

Choose IDs that sort, read well or stay unguessable, make retried requests safe with idempotency keys, and drop duplicate events in bounded memory.

  • Intermediate
  • 30 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free intro, Pro: 5 of 30 minutes free
Free: Why a design meets the same request twice. With a pass: the other 8 sections and 3 interview answers, about 25 minutes.

What you will learn

  • Choose random, time-ordered or readable IDs for each identifier a design needs
  • Generate codes that people type, with a uniqueness check and a secure source when they open something
  • Implement idempotency keys that return the stored reply when a request is retried
  • Detect a key reused for a different request, or still running, and answer each case
  • Handle duplicate events by event ID or by sender and sequence number, in bounded memory

Before you start

What you’ll learn · Before you start

What you will learn

  • Choose random, time-ordered or readable IDs for each identifier a design needs
  • Generate codes that people type, with a uniqueness check and a secure source when they open something
  • Implement idempotency keys that return the stored reply when a request is retried
  • Detect a key reused for a different request, or still running, and answer each case
  • Handle duplicate events by event ID or by sender and sequence number, in bounded memory

Before you start

On this page

IDs, idempotency keys and deduplication answer one question in three places: how does a design recognise the same thing when it arrives again? An ID names one object for its whole life, and its format decides whether IDs sort by creation time, can be read out over a phone or can be guessed. An idempotency key names one attempt at an operation: the client sends it with a request and again with every retry, and the server answers a retry with the stored reply instead of doing the work twice. Deduplication drops an event that was already handled, by remembering recent event IDs or the last sequence number of each sender, for as long as copies can still arrive.

Why a design meets the same request twice

Networks and people repeat themselves. A phone that sends a payment and hears nothing for 30 seconds cannot tell whether the request was lost or only its reply, so it sends the request again. A message broker that loses an acknowledgement delivers the same event a second time, and people press Pay twice. Retrying is the only safe move for a client that does not know the outcome, so the design has to make the repeat harmless. capture_twice.py sends one card payment twice, because the first reply was lost:

One payment sent twice: to a service that trusts every request, and to one that files requests by key Python · capture/capture_twice.py
"""One card payment, captured by a client whose first reply was lost, so it sends the same request again.

The trusting service charges every request it receives. The keyed service files each request under the
idempotency key the client sent with it: a retry with the same key gets the stored reply, and a request
that reuses a key for a different payment is refused. Amounts are integer minor units (paise)."""


class Gateway:
    """Stands in for the card network: every call moves money."""

    def __init__(self):
        self.charges = []

    def charge(self, card, amount):
        self.charges.append((card, amount))
        return f"ch_{len(self.charges)}"


class KeyReused(Exception):
    """The key was first used for a different request."""


class TrustingCapture:
    def __init__(self, gateway):
        self.gateway = gateway

    def capture(self, key, card, amount):  # the key is ignored
        return {"charge": self.gateway.charge(card, amount), "amount": amount}


class KeyedCapture:
    def __init__(self, gateway):
        self.gateway = gateway
        self.replies = {}  # idempotency key -> (the request it was first used for, the reply)

    def capture(self, key, card, amount):
        request = (card, amount)
        if key in self.replies:
            first, reply = self.replies[key]
            if first != request:
                raise KeyReused(f"key {key} was first used for amount {first[1]}")
            return reply  # a retry: the stored reply, and no second charge
        reply = {"charge": self.gateway.charge(card, amount), "amount": amount}
        self.replies[key] = (request, reply)
        return reply


for service in (TrustingCapture(Gateway()), KeyedCapture(Gateway())):
    first = service.capture("k-7f3a", "card-4242", 49_900)  # this reply is lost on the way back
    retry = service.capture("k-7f3a", "card-4242", 49_900)  # so the client sends the request again
    taken = sum(amount for _, amount in service.gateway.charges)
    print(f"{type(service).__name__}: first {first['charge']}, retry {retry['charge']}, "
          f"{len(service.gateway.charges)} charge(s), {taken} paise taken")

try:
    service.capture("k-7f3a", "card-4242", 59_900)  # same key, different amount
except KeyReused as refused:
    print("Reused key with another amount: refused,", refused)

Output

TrustingCapture: first ch_1, retry ch_2, 2 charge(s), 99800 paise taken
KeyedCapture: first ch_1, retry ch_1, 1 charge(s), 49900 paise taken
Reused key with another amount: refused, key k-7f3a was first used for amount 49900

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 capture_twice.py

The trusting service charged the customer twice. The keyed service charged once, answered the retry with the reply it had stored, and refused a request that reused the key for another amount, because one key names one request. Idempotency keys only work when the service looks the key up before it does any work:

Diagram: a service looks up the idempotency key of each request and takes one of three paths: do the work, replay the reply, or refuse.Clientsends a request with an idempotency key,and the same key again after a timeoutServicelooks up (client, key) in the key storebefore doing anythingNew keyclaim it, do the work once,save the reply under the keyKnown key, same requestreturn the saved reply;nothing runs againKnown key, different requestrefuse it:a key names one requestThe workcharge the card, credit the walletrequest + keynot foundfound, same fingerprintfound, other fingerprintexactly once

One request, one key, three paths

Text description of the diagram

A client sends a request together with an idempotency key, and sends the same key again whenever it retries after a timeout.

The service looks up the pair of client and key in its key store before doing any work, and takes one of three paths:

  • Not found: a new key. The service claims it, does the work once (for example, charges the card and credits the wallet) and saves the reply under the key.
  • Found, with the same request fingerprint: a retry. The service returns the saved reply, and nothing runs again.
  • Found, with a different fingerprint: the key is being reused for another request. The service refuses it, because a key names exactly one request.

A design that stays correct under repeats rests on a few choices, and each one has a plain job:

  • Random IDs (UUID version 4) carry 122 random bits, so any process can make one alone, and they reveal nothing.
  • Time-ordered IDs (UUID version 7) start with the creation time in milliseconds, so new IDs sort after old ones.
  • Readable codes use a short alphabet without look-alike characters, and each new code is checked for uniqueness.
  • Access codes, the ones that open something, come from a secure random generator.
  • Request fingerprints show a server that a known key has arrived with a different request.
  • Key claims record a key before the work starts, so a second copy in flight finds it.
  • Event-ID windows remember recent event IDs and drop copies that arrive within that time.
  • Sequence windows keep the highest sequence number of each sender, plus a short record below it.

Random or time-ordered IDs: what an index sees, measured Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Codes people read and type, with collision numbers
  • Codes that open something
  • Idempotency keys: claim, work, save
    • Two copies in flight, reproduced
    • What a reply is: fingerprints, scope and failures
    • Crashes between the writes, at every point
    • How long to keep keys
  • Deduplicating events in bounded memory
  • Practise: wallet top-ups that are safe to retry
    • Wallet top-ups that are safe to retry
  • Check yourself
  • Interview questions
    • When would you choose a version 7 UUID over a version 4 UUID, and when would you not?
    • A wallet app retries a top-up when the network times out. Support finds that about 3 in 10,000 top-ups are credited twice. Design the fix in the wallet service's classes, and say how you would test it.
    • Design the pickup codes for a network of 40,000 parcel lockers: what a code looks like, how it is generated, how long it lives and what limits wrong attempts. Today the codes are six digits from the random module and never expire.
    • A consumer drops duplicate order events by keeping their IDs in memory for 10 minutes. After an outage, the broker redelivered two days of events and some customers were charged twice. What went wrong, and how do you redesign deduplication so that an outage cannot do this again?
  • Key takeaways

5 runnable examples (JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 28 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Read a complete sample first: Low-level design: parking lot (the model)

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.