InterviewKata
System design · Worked design D46 of 46 · Warm-up · Long form

Design a unique ID generator

Design the service (or library) that gives every new order, message and photo a unique ID: a million IDs a second across three regions, no two ever the same, roughly in time order, and fitting in 64 bits so database indexes stay small. Module 20 explains the ID formats; this design is about running them: who hands out machine numbers, what happens when a clock jumps backwards, and when to avoid clocks altogether.

Modules this design uses20 Time, ordering and IDs · 21 Leases · 12 Indexes · 26 Security · Design D01

1. The prompt

"Design a system that generates unique IDs for a large distributed application."

What makes it hard:

2. Frame the problem

Must IDs be sortable by time?

Roughly: sorting by ID should give creation order to within a second or so. Exact global order isn't needed.

"Roughly" allows each machine to use its own clock; exact order would need coordination (Module 20).

How big can an ID be?

64 bits: it's the primary key of most tables and appears in every index and URL.

Rules out UUIDs (128 bits) as the main key; they stay an option elsewhere.

How many, where?

Peak 1 million IDs a second across 3 regions and about 2,000 application servers.

Decides how many machine numbers and how many IDs per millisecond each needs.

Will IDs be public?

Yes, in URLs (/orders/7203…).

Time-based IDs reveal creation time and volume; decide whether that matters (9.5).

How long must the scheme last?

At least 50 years.

Decides how many bits time gets, and the custom start date (epoch).

3. What it must do

  1. Hand out IDs that are unique across every machine and region, forever.
  2. Keep them roughly time-ordered, so new rows land at the end of indexes.
  3. Keep working when a region is cut off or the coordination service is down.

Not now: short human-friendly codes (that's D01), strictly gap-free numbering (invoices need a separate, per-company sequence for legal reasons).

4. How well it must do it

TargetWhy this number
Zero duplicates, ever data safetyA duplicate primary key loses data or fails a write in a way nobody expects.
Under 1 µs per ID, with no network call speedGenerating an ID must never be the slow part of a write.
Available as long as the app is surviving failuresIf IDs stop, every insert stops.
Sortable within ~1 s across machines consistencyGood enough for "newest first" and for index locality.
Lasts 50+ years evolutionChanging the ID scheme of a live system is a long migration.

5. Numbers that change the design

QuantityWorking→ so we…
Time241 ms ≈ 69.7 years→ so 41 bits of milliseconds from a custom epoch (e.g. 1 Jan 2026) last until 2095
Machines2,000 servers today, maybe 10,000 later; 210 = 1,024 is too few→ so either 13 machine bits, or IDs come from a few hundred generator processes, not every app server
Per machine per ms1 M/s ÷ 2,000 = 500/s = 0.5 per ms on average; bursts of 100× on one machine→ so 10–12 sequence bits (1,024–4,096 per ms) is plenty
Central counter1 M/s through one database sequence, cross-region round trips of 70–150 ms→ so no per-ID coordination; at most per-block (9.3)

The lab in section 13 lets you move the bits around and see the trade-offs.

6. The nouns

id (64 bits)     0 | ms since 2026-01-01 (41) | generator (12) | sequence (10)
generator_lease  /idgen/generators/{0..4095} → holder (host:pid), expires_at, last_ms_used   (etcd)
per process      last_ms, sequence                                                          (memory)

7. The contract

Library (preferred):   id = idgen.next()          // in-process, no network, ~100 ns
Service (alternative): POST /v1/ids?count=100     → [ids]    (for clients that can't embed the library)
Decode (support tools): GET /v1/ids/{id}          → {created_at, generator, sequence}

The library form keeps ID generation off the network entirely; the service form exists for languages or devices that can't hold a lease.

8. First working version

Each application process embeds the generator library. At start-up it claims a free generator number with a lease; after that, every ID is built from the clock, its number and a counter, entirely in memory.

8.1 A process starts and claims a number

8.2 Minting IDs in one millisecond

Parked for laterthe clock goes backwards (9.1) · running out of sequence (9.2) · IDs without clocks (9.3) · regions (9.4) · public IDs (9.5).

9. Make it hold up

9.1 When the clock goes backwards data safety

Run time sync that slews (speeds up or slows down the clock gradually) rather than stepping it, which makes backward jumps rare (Module 20). And never trust wall-clock time for correctness anyway: the "last millisecond used" check is what guarantees uniqueness.

9.2 Running out of sequence speed

With 10 sequence bits a generator can mint 1,024 IDs in one millisecond. The 1,025th waits until the next millisecond: at worst a sub-millisecond pause, about a million IDs a second per generator. If that's a real limit, give the busiest services more generator numbers, or more sequence bits and fewer generator bits. Never "borrow" from the next millisecond's numbers early without recording it, or a restart can reuse them.

9.3 IDs without clocks: handing out blocks surviving failures

ApproachWhy
✗ BadOne database sequence, called for every IDSimple and strictly ordered, but every insert in every region waits for one machine, and it's a single point of failure.
~ GoodRandom IDs (UUIDv4)No coordination at all, but 128 bits, not time-ordered (new rows land all over the index, Module 12), and unreadable.
✓ Great, depending on needsTime-based 64-bit IDs (this design) where order matters; or blocks from a counter: each process reserves 10,000 numbers at a time from a database sequence and hands them out from memoryBlocks need no clock and stay 64-bit and dense, at the cost of order between processes; when the counter is down, processes keep going until their block runs out. Flickr ran this with two counters (odd and even numbers) for availability.

9.4 Three regions surviving failures

Split the generator numbers by region (0–1,365 for region 1, and so on), so each region's coordination service hands out only its own range. A region cut off from the others keeps minting with no risk of collision, because no other region can hold its numbers. IDs from different regions are ordered by their clocks, which are within milliseconds of each other: "roughly sorted", as agreed.

9.5 IDs in public security

9.6 The whole design after the deep dives

10. Interviewer follow-ups

Why not just use UUIDv7?

UUIDv7 (RFC 9562) is time-ordered and needs no coordination at all, because 74 random bits make collisions vanishingly unlikely. If 128-bit keys are acceptable, it's the simplest answer. We chose 64 bits for smaller indexes and keys; that budget is what forces generator numbers and leases.

Moving an existing system from auto-increment IDs.

Start the new scheme above the current maximum (the time component makes new IDs far larger than old ones), switch writers one service at a time, and keep both formats valid in parsers. Foreign keys don't change, since both are 64-bit integers.

What if etcd is down when a process starts?

The process can't claim a number, so it doesn't start serving writes (fail closed). Running processes keep their leases for a while; if renewal fails past the lease, they stop minting rather than risk a duplicate.

A VM is snapshotted and restored twice.

Both copies believe they hold the same generator number: duplicates. Leases help (only one copy can renew), but the restored copies must re-claim a number on resume before minting; the library detects a large jump in monotonic time and re-registers.

Client-generated IDs for offline apps?

Let devices create UUIDv7s (no coordination possible offline) as the client-side key, and map them to server IDs when synced, or use the UUID as the key for that table.

11. When things break

FailureWhat users seeHow we detect itWhat limits the damage
Clock steps backwardsA few ms of waiting on one hostLibrary metric: backward jumpsRefuse until the clock passes the last used ms
Coordination service downNew processes can't startLease errorsRunning processes continue within their lease
Lease lost while runningThat process stops mintingRenewal failuresFail closed; restart claims a new number
Generator numbers exhaustedNew processes can't startFree-number gaugeAlert at 80%; free numbers of dead processes after lease expiry
Duplicate key on insertA write failsUnique-key violationsInvestigate immediately: it means a broken assumption

12. Wrap-up and the clock

Trade-offs we made:

What we'd watch: backward clock jumps · sequence exhaustion waits · free generator numbers · lease renewal errors · any duplicate-key error.

At 10×: more generator numbers (a bit from the sequence), or IDs minted by fewer, busier processes. The format must be decided once, so leave room up front.

A 45-minute interview

0–5Frame: size, order, volume, lifespan
5–12Options: sequence, UUID, blocks, time-based
12–22The bit layout, with the numbers
22–38Deep dives: generator numbers, clock going back (9.1), regions (9.4)
38–45Public IDs, follow-ups, wrap-up

A 60-minute interview

0–6Frame and numbers
6–14Options and the choice
14–24First version: claim a number, mint
24–50Deep dives 9.1–9.5
50–60Follow-ups, trade-offs, 10×
LevelWhat a strong answer shows
Mid-levelKnows the options (sequence, UUID, Snowflake) and their trade-offs; a sensible bit layout.
SeniorBit budget from the numbers, generator numbers via leases, clock-backwards handling, sequence exhaustion, library vs service.
Staff+Regions and partitions, VM snapshots, migration from an old scheme, public-ID leakage, when 128-bit UUIDv7 is the better answer.

Common mistakes:

13. Try it, check yourself, read more

The bit-budget lab. 63 bits to share between time, generator number and sequence. Move the sliders and see how long the scheme lasts, how many generators it allows and how fast each can mint.

Time bits
Time unit
Generator bits

Sequence bits are what's left of 63 (bit 64 is the sign, kept 0 so IDs are positive in every language). Targets from section 2: 50 years, 2,000–10,000 generators, bursts of a few hundred IDs per millisecond per generator.

A generator's clock jumps back by 5 ms. What should it do?

Minting with an earlier time could repeat IDs it already gave out in those milliseconds.

How should a generator get its unique number?

A lease guarantees one holder at a time and frees numbers when processes die; config files get copied.

When is UUIDv7 the simpler choice?

Time-ordered and coordination-free, at twice the size of a 64-bit key.
ResourceWhy
Twitter Snowflake (2010)The original 41 + 10 + 12 layout.
Discord API reference: SnowflakesA live variant, with 42 time bits and a 2015 epoch.
Flickr: ticket serversIDs from counters, with two servers for availability.
RFC 9562: UUIDs, including version 7The 128-bit time-ordered alternative.