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.

Why this piece earns its place

Worth naming what going in-process actually commits you to: the id format now ships on every service’s release cycle. The bit layout, the epoch and the clock guard are library code linked into each application, so the day any of them has to change you are running a fleet-wide redeploy, and for a long window old and new binaries mint ids side by side — which is only survivable if the change is compatible with ids already written. It also means one implementation per language your fleet speaks, and they have to agree bit for bit. If that is not realistic, the honest version of this design is a sidecar on each host: you keep the coordination-free property and a single implementation, and you pay a loopback call instead of a network round-trip. I would still start in-process, but I would keep the packing behind a small interface so moving to a sidecar is a swap rather than a rewrite.

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.

Why this piece earns its place

The knob this step introduces is the epoch, and you set it exactly once. 41 bits of milliseconds is a window of about 69 years that starts counting from whatever zero point you pick, so the useful move is to set the epoch to the day the system first deploys rather than to the Unix epoch — every year that passed before your first id existed is a year burned off the far end of the format. Set it in the future instead and early elapsed times go negative, inverting the ordering you just bought. The part people miss is that the epoch stops being configuration the moment an id is persisted: it is baked into every id already written, so moving it later does not shift new ids into fresh space, it re-points them at ranges you have already issued. Record the epoch beside the bit layout, treat both as a frozen wire format, and put the expiry date in a calendar instead of discovering it.

  • 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.

Why this piece earns its place

Ten bits for the machine field is a choice, not a law, and the thing to size it against is concurrently live generator processes, not hosts. Containers put several processes on one machine, and a rolling deploy runs the old and the new version at the same time, so peak demand for slots sits well above steady state — size for that overlap or you exhaust the 1,024 slots during a deploy rather than during growth. Widening the field is paid for out of the other two, and both bills are visible: bits taken from the sequence cut how many ids a node can mint inside a millisecond, bits taken from the timestamp cut how many years the format lasts. What makes the split worth settling carefully is that it is fleet-wide and permanent. Nothing in the id says where the boundaries are, so a service that decodes with a different split reads a different number out of the same bytes, and nothing complains.

  • 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.

Why this piece earns its place

This step quietly requires that reading the clock, comparing it against the last timestamp, bumping the counter and packing the result happen as one indivisible operation. They are separate pieces of state, and a node minting from many threads will interleave them — two threads read the same millisecond, both read the same counter, both emit the same id. So the whole mint sits under one lock or one compare-and-swap, which also makes the generator a contention point inside the process at precisely the moment it is busiest. The overflow path deserves the same care: past 4,096 ids in a millisecond the node has nothing to do but wait for the clock to tick, and a naive spin there burns a core and turns a capacity ceiling into a latency cliff for everything else on the box. If one process genuinely needs more than that, do not widen the sequence — give it a second machine id and mint from two independent generators.

  • 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.

Why this piece earns its place

The dangerous moment for a lease is not the registry going down — it is the registry deciding a generator is dead while it is still alive. A long garbage-collection pause or a partition can hold a process off the network past its renewal, the registry hands its slot to a starting instance, and now two live generators own the same slice of every millisecond and clash continuously for as long as both run. So I would deliberately bias reclamation the safe way: a generous lease, a cooling-off period before a released slot returns to the pool, and no reuse of the id of a process that merely stopped renewing. The asymmetry justifies it — a slot you leak costs capacity out of the 1,024 and surfaces as a generator that cannot start, which is loud and fixable; a slot you hand out twice costs uniqueness, which is silent and permanent. Better still, derive the id from a stable identity the orchestrator already assigns, so a restarting process deterministically gets its own slot back rather than racing for a new one.

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.

Why this piece earns its place

The guard’s memory lives in the process, and that is the gap worth closing out loud: it knows the last timestamp it served, and a restart wipes that. A machine whose clock steps backward and whose generator restarts a moment later comes up with no record of the future it has already minted into, and every check it makes passes while it re-issues a range it already served. Two things close it. Persist the last timestamp used and refuse to serve on startup until the clock passes it. And configure the host to slew rather than step — drag the clock gradually back into agreement instead of jumping it — so the guard is absorbing drift, which is small, rather than a correction, which need not be. That second one matters because refusing to mint is an outage: a node stalled for the length of a large correction should take itself out of rotation and let callers reach another generator, not hold their requests open.

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.

Why this piece earns its place

What makes this choice different from the others on this page is that it is close to irreversible. The width and type of an id spread outward — into every index, foreign key, API contract, client library and log format that touches one — so moving from a 64-bit integer to a 128-bit UUIDv7 later is not a change to the generator, it is a migration of every table that stores one. The properties to settle before the first id is persisted are exactly the ones you cannot retrofit: how wide the id is, and whether the leading bits carry time. What stays genuinely reversible is where the coordination lives, because a ticket server or a range allocator can be introduced behind the same 64-bit shape if strict ordering turns out to matter later. So the question I would actually ask is whether the storage layer is index-bound. If it is, the ordering and the narrower id earn the machine-id assignment. If it is not, UUIDv7 buys the same time ordering with no registry to run, and the only cost is bytes.

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…

    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…

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

  3. The machine-id field guarantees that…

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

  4. The sequence counter exists to…

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

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

    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.

The answer, out loud

What a strong answer to “Design a Distributed ID Generator (Snowflake)” sounds like, first question to last trade-off. It is about 7 minutes of talking; the whiteboard and the interviewer fill the rest of the 45. Read it aloud once, then close the page and give it yourself.

  1. 0–4 min

    What the hard part actually is

    Let me say what I think we’re building before I draw anything. Somebody needs a unique identifier every time they create a row or a message or an event, across a lot of machines, and they need one constantly. So the hard part isn’t uniqueness on its own — a big random number gets you that. It’s uniqueness with no conversation between machines, cheap enough to sit on the hot path of every insert, and ideally ids that sort roughly by when they were made. I’ll assume a 64-bit integer is the target shape and come back to why at the end.

  2. 4–9 min

    Generate the id in the service

    The first real call is where ids come from. A database auto-increment column is one counter, so the entire fleet queues behind a single writer, and it takes the whole system down with it. A central id service fixes the ownership question but not the physics — now every insert waits on a network round-trip. I’d rather mint in the service itself, in memory, and return immediately. And once I say that, the whole rest of the design collapses into one question: how do I fill 64 bits so that two machines never choose the same one without talking to each other?

    Built in step 1: Local, no round-trip
  3. 9–14 min

    Time in the top bits

    I’ll start at the top of the integer with a timestamp in milliseconds. It goes first because the most significant bits are what decide ordering, so an id minted later is simply a bigger number. That buys two things at once: new rows land at the end of the index instead of scattering across it the way random UUIDs do, and I can range-scan by time without keeping a separate column. I’d flag straight away that this is roughly sorted, not strictly sorted — machines don’t agree on the clock down to the millisecond, and I don’t need them to.

    Built in step 2: Timestamp in the high bits
  4. 14–20 min

    A field per machine

    Time alone isn’t unique, because two machines minting in the same millisecond produce the same leading bits. I could have them coordinate, but that’s the network hop I just deleted. I could pad the id with randomness and hope, but hope stops working exactly when volume goes up. So instead every generator gets its own number and it goes in the middle of the id. Now two machines in the same millisecond differ in those bits by construction — not probably, definitely. Ten bits is the usual split, which is 1,024 generators, and it moves all of the coordination into one moment: whoever hands that number out.

    Built in step 3: A machine ID field
  5. 20–26 min

    A counter inside the millisecond

    One machine also needs more than one id per millisecond. If I stop here, a node is capped at about a thousand ids a second, which isn’t a system, it’s a bottleneck. So the low bits are a counter that increments per id and resets every time the clock ticks. Twelve bits gives a single node 4,096 ids inside one millisecond, roughly four million a second, and the only moment it ever waits is if it fills that before the millisecond is over. That’s the whole layout — sign bit, time, machine, counter — and everything after this is about the two things I’ve been quietly assuming.

    Built in step 4: The sequence counter
  6. 26–32 min

    Who hands out machine numbers

    The first assumption is that every generator has a different machine number. If two ever share one they don’t collide occasionally, they collide every millisecond for as long as both are running. So I won’t let a process choose its own number at random — across 1,024 slots that goes wrong far sooner than intuition suggests. Something authoritative hands it out at startup and the process holds it as a lease: ZooKeeper, etcd, or the orchestrator if it already gives me a stable ordinal per instance. That is the only coordination in the design, and it happens once per process rather than once per id.

    Built in step 5: Assign node bits safely
  7. 32–39 min

    When the clock goes backwards

    The second assumption is that the clock only moves forward, and that one is just false. NTP corrects machines, and a correction can step the clock backward. When it does, the generator starts handing out timestamps it already used, and the single guarantee this system exists to provide has quietly stopped holding. The fix itself is small: remember the last timestamp minted, and if the clock is behind it, don’t mint — wait, or refuse. I want to be explicit that this is a genuine trade and not a free win. I’m choosing to be briefly unavailable rather than wrong, and for identifiers that’s the side I want to land on.

    Built in step 6: Clock skew & rewind
  8. 39–45 min

    What this gives up

    To close it out: this design buys speed and ordering by giving up two things. It leaks creation time, which anyone holding an id can read, and it’s only roughly ordered, so anyone who needs strict global ordering has to accept a central allocator and the coordination that comes with it. If you’d rather not run a registry at all, UUIDv7 gets you time-ordered ids with no machine assignment, just wider. With more time I’d spend it on operating this rather than designing it — nothing here tells you a duplicate ever happened, so I’d want a way to detect one after the fact, and I’d want to know what backfilling ids for rows that already exist looks like.

    Built in step 7: Alternatives & trade-offs

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