Home › Blog › Unique ID Generator

Design a Unique ID Generator: Snowflake IDs Explained

How to answer the unique ID generator question: requirements, the four standard options, the Snowflake bit layout, clock skew, multiple datacenters and the trade-offs between them.

The standard answer to "design a unique ID generator" for a distributed system is a Snowflake-style design: a 64-bit integer built from a millisecond timestamp, a machine (worker) ID and a per-millisecond sequence number. Every server mints IDs locally with no per-ID coordination, the IDs sort roughly by creation time, and the main risk you have to handle is the system clock moving backward. Before you commit, show why UUIDs, database auto-increment and a ticket server fall short.

The specifics below come from Twitter's Snowflake README and source code, Flickr's ticket server post, Discord's API docs, the Sonyflake README and RFC 9562, the current UUID standard.

1. Clarify the requirements first

"Generate unique IDs" is underspecified on purpose. Ask before you draw anything:

When Twitter built Snowflake in 2010, it was moving storage from MySQL to Cassandra, which has no built-in sequential ID facility. The requirements in its README are a good model answer to write on the board:

2. The four options and their trade-offs

UUID. A UUID is 128 bits. Version 4 is 122 random bits, so any machine can generate one with no coordination and collisions are practically impossible. The costs: it is twice the size of a 64-bit key, and random values scatter inserts across a B-tree index. Flickr rejected GUIDs for exactly that reason: they are large and index poorly in MySQL. RFC 9562 (May 2024) added UUIDv7, which puts a 48-bit Unix millisecond timestamp in the high bits so values sort by time and index more locally. It is a strong answer when 128 bits is allowed; it does not meet a 64-bit requirement.

Database auto-increment. One database handing out AUTO_INCREMENT values is simple and strictly ordered, but it is a single point of failure and a write bottleneck. The multi-master variant gives N databases the same step: with two servers, one issues odd numbers and the other even. That removes the single point of failure but breaks ordering across servers and makes adding a server painful, because every step and offset has to change.

Ticket server. Flickr's 2010 design is a dedicated MySQL database with a one-row table. An app server runs REPLACE INTO Tickets64 (stub) VALUES ('a'); then SELECT LAST_INSERT_ID(); to get the next 64-bit value. For availability Flickr ran two ticket servers, one issuing odd and one even values (auto-increment-increment = 2, offsets 1 and 2), with clients round-robining between them; the two drift apart, so IDs are not strictly sequential. A common refinement is range allocation: an app server reserves a block of IDs at a time, cutting network calls but losing cross-server ordering and wasting the rest of a block on a crash.

Snowflake. Each worker combines its clock, its own worker ID and a counter. No network call per ID, 64-bit numeric, roughly time-sortable. The costs: it depends on the clock, each worker needs a unique ID, ordering across machines is only approximate, and anyone holding an ID can read its creation time.

ApproachSizeSorts by time?CoordinationMain weakness
UUIDv4128 bitsNoNoneLarge; random index inserts
UUIDv7128 bitsYes (ms)NoneFails a 64-bit requirement
Single DB auto-increment64 bitsYes, strictlyEvery ID hits one databaseSingle point of failure, throughput ceiling
Multi-master with step64 bitsNot across serversFixed step and offset per serverHard to add servers
Ticket server64 bitsRoughlyNetwork call per ID or per blockCentral dependency, extra hop
Snowflake64 bitsRoughly (k-sorted)Worker ID assignment onlyDepends on the clock

If the requirements are 64-bit, numeric, sortable and highly available, Snowflake is the only row that satisfies all of them. Say that out loud before you move on.

3. The Snowflake bit layout

The original layout uses 63 of the 64 bits. The top bit stays 0 so the value is positive in signed 64-bit types.

 1 bit      41 bits                       10 bits       12 bits
+------+--------------------------------+-------------+-------------+
|  0   |  ms since custom epoch         |  worker ID  |  sequence   |
+------+--------------------------------+-------------+-------------+
 bit 63  bits 62-22                      bits 21-12    bits 11-0
FieldBitsWhat it buys you
Sign1Always 0, so the ID is never negative in a long or BIGINT
Timestamp41241 ms is about 69 years from your chosen epoch
Worker ID101,024 distinct generators
Sequence124,096 IDs per millisecond per worker

Do the math out loud. 4,096 IDs per millisecond is a ceiling of about 4.1 million IDs per second per worker, and 1,024 workers is roughly 4.2 billion per second in total, far above a 10,000-per-second requirement. The epoch matters too: counting from 1970 would already have used almost 57 of the 69 years, and the IDs would overflow in 2039. Pick a custom epoch close to launch; Twitter's own code counts from November 2010, Discord's from the start of 2015.

The generator itself is a few lines. It must run under a lock (or on a single thread) per worker ID:

EPOCH = 1735689600000      # example: 2025-01-01 UTC in ms
last_ts = -1
seq = 0

def next_id():
    global last_ts, seq
    ts = now_ms()
    if ts < last_ts:
        ts = handle_clock_backward(last_ts)   # see section 5
    if ts == last_ts:
        seq = (seq + 1) & 4095               # 12-bit wrap
        if seq == 0:                          # 4,096 used this ms
            ts = wait_until_after(last_ts)
    else:
        seq = 0
    last_ts = ts
    return ((ts - EPOCH) << 22) | (worker_id << 12) | seq

Because the timestamp sits in the high bits, (id >> 22) + EPOCH recovers the creation time from any ID. That helps with debugging and time-range queries, and it is also an information leak worth mentioning.

4. Assigning worker IDs

A correctly implemented Snowflake generator can produce a duplicate in only two ways: two live processes share a worker ID, or one worker's clock goes backward. Worker ID assignment handles the first. Common choices:

Whichever you pick, say how you prevent a restarted or cloned process from reusing an ID that is still live.

5. Handling clock skew

Clocks can move backward, for example after an NTP step correction or a manual change. Skew between machines only hurts ordering, because different worker IDs keep the values distinct. A single worker's clock going backward can hurt uniqueness, because it could reissue a timestamp-and-sequence pair it has already used.

  1. Refuse to generate until time catches up. This is what Twitter's Snowflake does: if the clock goes back, it rejects ID requests until the current time passes the timestamp of the last ID it generated.
  2. Size the response to the jump. For a gap of a few milliseconds, waiting it out costs callers almost nothing. For a large jump, fail fast, alert, and take the node out of rotation rather than stalling requests.
  3. Configure NTP to avoid stepping backward. The Snowflake README recommends running NTP in a mode that never moves the clock back, so small corrections are applied gradually.
  4. Survive restarts. A fresh process does not know its last timestamp. Persist a high-water mark periodically, or wait a safety margin on startup, so a reboot after a clock change does not repeat IDs.
  5. Or run on logical time. Keep issuing from the last timestamp, running slightly ahead of the wall clock until real time catches up. This trades accuracy of the embedded time for availability.

The one-line summary: Snowflake buys zero per-ID coordination at the price of depending on clocks. Raise clock skew before the interviewer does.

6. Going multi-datacenter

The usual move is to split the 10 machine bits into a datacenter field and a worker field. Twitter's own open-source IdWorker does exactly this: 5 datacenter bits and 5 worker bits, for 32 datacenters with 32 workers each. Discord's documented layout splits the same 10 bits into a 5-bit internal worker ID and a 5-bit internal process ID. What that buys you:

More generators means taking bits from somewhere: fewer sequence bits (lower per-worker rate) or fewer timestamp bits (shorter lifetime).

7. Real variations worth knowing

SchemeSizeTime fieldNode fieldCounter / random
Twitter Snowflake64 bits41 bits, ms10 bits12-bit sequence
Discord snowflake64 bits42 bits, ms since the start of 20155-bit worker + 5-bit process12-bit increment
Sonyflake63 bits used39 bits, 10 ms units (about 174 years)16 bits8-bit sequence
ULID128 bits48 bits, msNone80 random bits
UUIDv7128 bits48 bits, msNoneRandom, minus version and variant bits

Two details make good follow-up material. Sonyflake gives up per-machine throughput (28 IDs per 10 ms per instance) for a longer lifetime and more machines; those are its defaults, and its v2 lets you change the bit widths and time unit. Discord returns its IDs as strings in its HTTP API to prevent integer overflow in some languages; JavaScript numbers are only exact up to 253 − 1, so a 64-bit ID sent as a JSON number can be silently rounded.

8. How to present it, and the follow-ups to expect

  1. Agree on requirements: 64-bit, numeric, sortable, highly available.
  2. Rule out the alternatives in a sentence each.
  3. Draw the bit layout; do the capacity and lifetime math aloud.
  4. Walk through next_id(), including sequence overflow.
  5. Raise clock skew and worker ID assignment unprompted.
  6. Close with the multi-datacenter split and trade-offs.

Likely follow-ups, with short answers:

Knowing the design and explaining it under time pressure are different skills, so rehearse it aloud. A free AI mock interview in CoPilot Interview can include system design questions in the mix, reads each one aloud, and scores your spoken answer with what worked and what to improve. For the surrounding concepts, review the 14 core system design interview concepts.

Rehearse this design out loud

Paste a job description and your résumé, add system design to the question mix, and get a score on every answer plus an interview report with likely next-round questions. The free plan includes 15 minutes a day with no credit card.

Try a free AI mock interview

FAQ

What is a Snowflake ID?

It is a 64-bit ID format Twitter released in 2010: a 41-bit millisecond timestamp, a 10-bit machine ID and a 12-bit sequence number, with the top bit left at zero. Because the timestamp sits in the high bits, IDs sort roughly by creation time without machines coordinating with each other.

Why not just use a UUID?

A UUID is 128 bits, twice the size of a 64-bit key, and random UUIDv4 values scatter inserts across a B-tree index. If 128 bits is allowed, a time-ordered UUIDv7 from RFC 9562 is a reasonable choice; if the requirement is a 64-bit numeric ID, use a Snowflake-style design.

How many IDs per second can a Snowflake generator produce?

The 12-bit sequence allows 4,096 IDs per millisecond per worker, a ceiling of about 4.1 million per second per worker. Twitter's stated requirement was at least 10,000 per second per process, so the layout leaves a lot of headroom.

What happens if the clock goes backward?

Twitter's Snowflake refuses to generate IDs until the clock passes the timestamp of the last ID it issued, and its README recommends running NTP in a mode that does not move the clock backward. In an interview, also describe what you do for a large jump: alert and take the node out of rotation.

Are Snowflake IDs strictly ordered?

No. IDs from one worker always increase, but IDs from different machines are only roughly ordered, limited by clock differences between them. Twitter's target was ordering within about one second, aiming for tens of milliseconds.