Vibe Engines
YouTube
System Design

Design an ID Generator

Step 1 / 9

Learn system design by building a distributed unique ID generator like Twitter Snowflake step by step.

The numbers to beat41 bitstimestamp~69 yrsof msk-sortedby time

The whole design, in writing

Learn system design by building a distributed unique ID generator like Twitter Snowflake step by step. An interactive guide covering why auto-increment and UUIDs fall short, the timestamp/machine/sequence bit layout, time-sortable 64-bit IDs, machine-id assignment, clock skew, and alternatives.

Every step of the build above, written out: the problem each piece solves, the option that was taken and the ones that were not, the numbers, and how it fails in production.

The big idea

Why is this hard?

“Give me a unique ID” sounds trivial — until it must work across hundreds of machines, millions of times a second, with zero collisions, and ideally produce IDs that sort by time. A single counter can’t keep up, and randomness has its own costs.

Serviceneeds an ID
New in this step: Service.

Generate IDs locally on each machine with no per-ID coordination, by carving a 64-bit integer into fields — time, machine, sequence — that together guarantee uniqueness. This is Twitter’s Snowflake, and it’s mostly a packing problem.

What the new pieces do

Serviceclient
Any service creating a row, message or event that needs a unique identifier — millions of times a second, across many machines.

Step 1 · The naive options

Local, no round-trip

A database auto-increment is one counter — a single point of failure and a write bottleneck the whole fleet must queue behind. Calling a central service per ID adds a network hop to every insert.

ID Generator64-bit ID
New in this step: ID Generator, 64-bit ID. · swipe to pan the diagram

You need unique IDs millions/sec across hundreds of machines. Where do you generate them?

  1. One counter is a single write bottleneck and SPOF the whole fleet queues behind. It can’t keep up with millions/sec across many machines.

  2. Correct and ordered, but it adds a network round-trip to every single insert and is itself a bottleneck/SPOF. The hot path can’t afford a hop per ID.

  3. Each service generates IDs locally with no network call — speed is the point. The rest is just deciding how to fill 64 bits so they never collide.

Make the generator local: a library inside each service that produces a 64-bit ID in-process, instantly, with no network call. The rest of the design is just deciding how to fill those 64 bits so they never collide.

What the new pieces do

ID Generatorbackend
Runs as a library inside each service (or a nearby sidecar). It produces IDs locally with no network round-trip per ID — speed is the whole point.
64-bit IDid
The three fields packed into one 64-bit integer: sign bit, timestamp, machine id, sequence. Compact, index-friendly, and time-sortable.

Back of the envelope

64-bit int vs 128-bit UUID
half the size ⇒ smaller, faster DB indexes
local mint = 0 network hops
no per-ID round-trip on the hot path
1 counter ⇒ bottleneck + SPOF
why DB auto-increment can’t scale the fleet

Step 2 · Put time first

Timestamp in the high bits

Random IDs (like a UUIDv4) are unique but unordered — inserting them scatters writes all over a database index, hurting cache locality and range queries. You lose all sense of “newer” vs “older”.

ID GeneratorTimestamp64-bit ID
New in this step: Timestamp. · swipe to pan the diagram

You want IDs that don’t scatter DB index writes and that sort by creation time. How?

  1. Unique, but unordered — random inserts scatter across the index, hurting cache locality and range scans, and you lose newer-vs-older. Also twice the size of a 64-bit int.

  2. Later IDs are numerically larger, so they’re roughly time-sortable and new inserts append to the end of the index. 41 bits of ms covers ~69 years from a custom epoch.

  3. Time in the low bits means the most-significant bits don’t increase with time, so IDs don’t sort by creation order — defeating the whole reason to include time. Time must lead.

Place a millisecond timestamp in the top bits. Now IDs minted later are numerically larger, so they’re roughly time-sortable and new inserts append to the end of the index. 41 bits of milliseconds covers ~69 years from a custom epoch.

  • 41 bitstimestamp
  • ~69 yrsof ms
  • k-sortedby time

What the new pieces do

Timestampfield
Milliseconds since a custom epoch in the high bits. Putting time first makes IDs increase over time, so they roughly sort by creation order.

Back of the envelope

41 bits of ms ≈ 69 years
from a custom epoch, in the high bits
time leads ⇒ k-sorted IDs
new inserts append to the index tail
~k-sorted, not perfectly ordered
clocks differ slightly across machines

Step 3 · Separate the machines

A machine ID field

Two machines minting an ID in the same millisecond would produce the same timestamp bits — and collide. Coordinating between them on every ID would re-introduce the network hop we just removed.

ID GeneratorMachine ID64-bit ID
New in this step: Machine ID. · swipe to pan the diagram

Two machines minting in the same millisecond share timestamp bits and collide. Fix without a network hop?

  1. Per-ID coordination re-introduces the network round-trip you removed in step 1. Uniqueness should come from structure, not a conversation per ID.

  2. Hope isn’t a guarantee — birthday-paradox collisions appear at high rates, and you’d need many random bits, bloating the ID. You want collision-free by construction.

  3. Same millisecond, different machine bits → different IDs. Each node owns its slice of every millisecond, so they physically can’t collide — coordination happens once at assignment, not per ID. 10 bits = 1,024 generators.

Give each generator a unique machine ID in the middle bits. Same millisecond, different machines, different IDs — uniqueness by construction, with no per-ID coordination. 10 bits allows 1,024 distinct generators.

  • 10 bitsmachine id
  • 1,024generators
  • 0per-ID coordination

What the new pieces do

Machine IDfield
A unique number per generator instance. Because each machine owns its slice, two machines in the same millisecond never collide — no coordination per ID.

Step 4 · Many IDs per millisecond

The sequence counter

One machine often needs more than one ID per millisecond. Timestamp + machine bits alone would repeat for every ID minted within the same millisecond on the same node.

ID GeneratorMachine IDSequence64-bit ID
New in this step: Sequence. · swipe to pan the diagram

One machine often needs many IDs in the same millisecond. Timestamp + machine bits would repeat. Fix?

  1. Capping a node at one ID per millisecond (1,000/sec) is far too slow. You need many IDs within a millisecond without waiting on the clock each time.

  2. A counter that increments per ID and resets each millisecond lets one machine mint up to 4,096 IDs/ms (12 bits, ~4M/sec). Only if it overflows do you wait for the next tick.

  3. Thread ids aren’t bounded or unique across processes and can repeat, so they don’t guarantee uniqueness within a millisecond. A reset-per-ms counter is exact and compact.

Add a sequence counter in the low bits that increments per ID and resets each millisecond. With 12 bits, a single machine can mint 4,096 IDs per millisecond (~4M/sec). If it overflows, wait for the next millisecond tick.

  • 12 bitssequence
  • 4,096IDs / ms / node
  • ~4Mper second

What the new pieces do

Sequencefield
A counter reset each millisecond, so one machine can mint up to 4,096 IDs in the same millisecond before it must wait for the clock to tick.

Back of the envelope

12 bits ⇒ 4,096 IDs / ms / node
~4M IDs/sec per machine
[1 | 41 time | 10 node | 12 seq]
the full 64-bit layout
seq overflow ⇒ wait 1 ms tick
the only stall on a single node

Step 5 · Hand out machine IDs

Assign node bits safely

The whole scheme breaks if two generators are accidentally given the same machine ID — then they collide every millisecond. So who assigns those 1,024 ids, and how do you avoid duplicates as machines come and go?

ID Generatorcoordination-freeMachine ID10 bits · nodeSequence12 bits · per msID Registryassign machine ids
New in this step: ID Registry.

The scheme breaks if two generators get the same machine id. Who assigns the 1,024 ids?

  1. Random picks collide (birthday paradox at 1,024 slots), and two colliding generators then clash every millisecond. Assignment must guarantee uniqueness, not gamble on it.

  2. Each generator claims a free id once on startup and holds it via a lease. That’s the only coordination in the whole system — after it, every node runs independently and collision-free.

  3. Static config works until autoscaling, re-IPing or cloning silently duplicates an id. A lease-based registry handles machines coming and going without accidental reuse.

Use an ID Registry: on startup each generator claims a free machine id from a coordination service (ZooKeeper/etcd) or static config, holding it via a lease. The only coordination in the entire system happens here, once per process.

What the new pieces do

ID Registryservice
Hands each generator a unique machine id on startup (via ZooKeeper/etcd or config), so no two instances ever share the same node bits.

Step 6 · When the clock lies

Clock skew & rewind

IDs trust the wall clock — but NTP can step the clock backward. If time moves back, a machine could reuse timestamps it already used and mint duplicate IDs. The one assumption Snowflake makes is the one thing clocks violate.

ID GeneratorMachine IDSequence64-bit IDID RegistryClock Guard
New in this step: Clock Guard. · swipe to pan the diagram

NTP can step the wall clock backward. What stops the generator minting duplicate IDs?

  1. Rare isn’t never: one NTP correction can reuse a whole millisecond range of IDs, producing duplicates — the one failure this system must never have.

  2. Time zones aren’t the issue — NTP corrections step the underlying wall clock backward regardless of zone. You must detect and guard rewind, not relabel the clock.

  3. A clock guard remembers the last value used and stalls/refuses until the clock passes it — never issuing an ID for a time it already served. A tiny rare pause buys an ironclad no-duplicates guarantee.

A Clock Guard tracks the last timestamp used. If the clock goes backward, the generator refuses to mint (or briefly stalls) until the clock catches up past the last value — never issuing an ID for a time it has already served.

What the new pieces do

Clock Guardguard
Watches for the wall clock jumping backward (NTP correction). If time moves back, it refuses to mint until the clock catches up, preventing duplicate IDs.

Step 7 · Other ways

Alternatives & trade-offs

Snowflake gives unique, k-sorted IDs without coordination — but it leaks creation time (a privacy/competitive signal) and isn’t strictly monotonic across machines. Sometimes you need different properties.

ServiceID GeneratorTimestampMachine ID64-bit IDTicket Server
New in this step: Ticket Server. · swipe to pan the diagram

Pick the tool for the job: a DB ticket server or range allocator (hand out blocks of ids per machine) gives strict ordering at the cost of a central component; UUIDv7 offers time-ordered randomness with no machine-id assignment. Snowflake wins when you want local, fast, sortable, and 64-bit.

What the new pieces do

Ticket Serverstore
An alternative: a database (or range allocator) that hands out monotonically increasing ids. Simpler, strongly ordered, but a potential bottleneck and SPOF.

You did it

You just designed an ID generator.

ServiceID GeneratorTimestampMachine IDSequence64-bit IDID RegistryClock GuardTicket Server
The finished design, end to end. · swipe to pan the diagram

Everything you assembled, in order

  • IDs are generated locally, in-process, with no per-ID network round-trip.
  • A timestamp in the high bits makes 64-bit IDs roughly time-sortable.
  • A machine-id field keeps same-millisecond IDs on different nodes disjoint.
  • A per-millisecond sequence counter handles bursts (4,096 IDs/ms/node).
  • A registry assigns unique machine ids once at startup — the only coordination.
  • A clock guard refuses to mint when the wall clock rewinds, avoiding duplicates.
  • Ticket servers, range allocators and UUIDv7 are alternatives with other trade-offs.

Where an interviewer pokes next

Getting the boxes right is the easy half. These are the questions that separate a candidate who drew the diagram from one who has run the thing. Answer each one out loud before you open it.

  1. Why not just use UUIDs everywhere?

    UUIDv4 is random: unique without coordination, but unordered and 128 bits. The randomness scatters DB index inserts (poor locality, page splits) and doubles index size versus a 64-bit Snowflake. UUIDv7 fixes ordering by embedding time — fine when you don’t want to assign machine ids — but Snowflake stays smaller with explicit machine/sequence fields.

  2. What information does a Snowflake ID leak?

    The embedded timestamp reveals creation time, and sequential ids can leak volume/ordering (a competitor can estimate how many objects you create per second). If that’s sensitive, encrypt/permute the id externally, use UUIDv7-style randomness, or add random bits — trading sortability for opacity.

  3. Is a Snowflake ID strictly monotonic / globally ordered?

    No — it’s "k-sorted." Within one machine ids strictly increase, but across machines clocks differ by a few ms, so two ids minted "at the same time" on different nodes may not order by true wall-clock. Fine for feeds and range scans; strict global order needs a central allocator and its coordination cost.

  4. You run out of machine-id bits (>1,024 generators) — now what?

    Re-budget the 64 bits: shrink the sequence or timestamp range to widen the machine field, or reuse ids via short leases so transient instances share a pool (a dead machine releases its id). Many systems scope ids as dc-bits + worker-bits. The bit budget is fixed; you allocate it to your scale.

  5. A ticket server / range allocator vs Snowflake — when?

    Use a range allocator (hand each machine a block of ids from a central counter) when you need strictly increasing ids and can tolerate a central component and occasional gaps. It coordinates per block, not per id, so it’s cheaper than a per-id service. Snowflake wins when you want zero central dependency on the hot path and only need k-sorted, time-encoded 64-bit ids.

Check yourself — the answers, and why

Eight steps in, these are the calls you should be able to make cold. Pick one, then read why.

  1. Snowflake generates IDs locally (in-process) mainly to…

    • Save storage
    • Avoid a network round-trip per ID
    • Encrypt the ID

    A central service or DB counter adds a hop and a bottleneck to every insert.

  2. The timestamp goes in the HIGH bits so that…

    • IDs are smaller
    • IDs sort by creation time and append to the index
    • Time is hidden

    Most-significant time bits make later IDs numerically larger — roughly time-ordered.

  3. The machine-id field guarantees that…

    • IDs are random
    • Same-millisecond IDs on different nodes don’t collide
    • Clocks stay in sync

    Each node owns a disjoint slice of every millisecond — unique by construction, no per-ID coordination.

  4. The sequence counter exists to…

    • Order across machines
    • Allow many IDs within one millisecond on a node
    • Detect clock skew

    12 bits → 4,096 IDs/ms/node; it resets each millisecond.

  5. When the wall clock steps backward, a Snowflake generator should…

    • Keep minting normally
    • Refuse to mint until time passes the last used value
    • Switch to UUIDs

    Reusing an already-served timestamp would duplicate IDs — the clock guard waits out the rewind.

How you’d open this design in an interview

Before any boxes: agree what it must do, pin the qualities that shape everything, then build — naming each trade-off as you make it. The walkthrough above is that exact order.

What it must do

Agree on these before drawing a single box.

  • Mint an ID: hand back a unique 64-bit integer, locally, millions of times a second.
  • No collisions: two machines minting in the same millisecond never produce the same id.
  • Time-sortable: ids minted later are numerically larger, so they roughly sort by creation order.
  • Burst within a millisecond: a per-ms sequence lets one node mint thousands of ids before waiting for the clock.
  • Assign machine ids: give each generator a unique node id once at startup.

The qualities that shape everything

Each one names the mechanism that buys it.

No network hop per id
The generator runs as a library inside each service and produces a 64-bit id in-process, so there’s no round-trip on the hot path.
Roughly time-ordered, index-friendly ids
A millisecond timestamp in the top bits makes later ids numerically larger, so they’re k-sorted and new inserts append to the index tail.
Same-millisecond ids never collide, coordination-free
Each generator owns a unique machine-id slice of every millisecond, so two nodes physically can’t produce the same number — no per-id coordination.
Many ids within one millisecond
A per-millisecond sequence counter in the low bits lets one node mint 4,096 ids/ms, only stalling to the next tick on overflow.
No two generators share a machine id
An ID registry leases each generator a free machine id once at startup (ZooKeeper/etcd), the only coordination in the whole system.
No duplicates when the clock rewinds
A clock guard tracks the last timestamp used and refuses to mint if the wall clock steps backward, until time passes the last value.

The trade-offs you say out loud

Senior signal isn’t the boxes — it’s naming what you gave up and why it was the right price.

A local, in-process 64-bit generator over a database auto-increment column

One counter is a write bottleneck and single point of failure the whole fleet queues behind, and a central id service adds a network hop to every insert. Minting locally has zero per-id coordination — speed is the point.

A millisecond timestamp in the high bits over random 128-bit UUIDv4s

Random ids are unordered, so inserts scatter across the DB index (poor locality, page splits, no range scans) and cost twice the size. Time-leading bits make ids k-sorted so new inserts append to the index tail.

A per-generator machine-id field over coordinating between machines on each id

Per-id coordination re-introduces the network hop you just removed, and random low bits gamble on birthday-paradox collisions. Disjoint machine slices make ids unique by construction — coordinate once at assignment, not per id.

A registry that leases a machine id at startup over each generator picking a random machine id

Random picks collide across 1,024 slots (birthday paradox), and two colliding generators then clash every millisecond. A lease guarantees uniqueness and handles machines coming and going without accidental reuse.

A clock guard that refuses to mint on rewind over assuming clock rewind is too rare to handle

One NTP correction can reuse a whole millisecond range of ids — the one failure this system must never have. Tracking the last timestamp and waiting out the rewind trades a tiny, rare pause for an ironclad no-duplicates guarantee.

What this teaches

Learn system design by building a distributed unique ID generator like Twitter Snowflake step by step. An interactive guide covering why auto-increment and UUIDs fall short, the timestamp/machine/sequence bit layout, time-sortable 64-bit IDs, machine-id assignment, clock skew, and alternatives.

Key takeaways

  • IDs are generated locally, in-process, with no per-ID network round-trip.
  • A timestamp in the high bits makes 64-bit IDs roughly time-sortable.
  • A machine-id field keeps same-millisecond IDs on different nodes disjoint.
  • A per-millisecond sequence counter handles bursts (4,096 IDs/ms/node).
  • A registry assigns unique machine ids once at startup — the only coordination.
  • A clock guard refuses to mint when the wall clock rewinds, avoiding duplicates.
  • Ticket servers, range allocators and UUIDv7 are alternatives with other trade-offs.

Concepts covered

  • Why is this hard?
  • Local, no round-trip
  • Timestamp in the high bits
  • A machine ID field
  • The sequence counter
  • Assign node bits safely
  • Clock skew & rewind
  • Alternatives & trade-offs
RUN IT YOURSELF

Snowflake IDs, in Python & TypeScript

Snowflake packs a timestamp, machine id, and sequence into one 64-bit id — unique, sortable, and generated without coordination. Here it is in both languages, running live. Switch tabs, read the comments, and hit Run.

HOW TO READ THE CODE — 4 IDEAS
  1. A 64-bit id is split into fields: 41 bits time | 10 bits machine | 12 bits sequence.
  2. Build an id by shifting each field into place and OR-ing them together (makeId).
  3. Read fields back by shifting + masking (steps 1–3).
  4. Because time is the high bits, ids are roughly time-sortable — and never collide across machines.
CPython · WebAssembly
built to be packed, not memorized — make the calls, rewind the clock, run the gauntlet.
Finished this one? 0 / 65 System Designs done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More System Designs