Snowflake-style ID generator
On the roadmap:Beginner · step 9: Your first design
Pack a timestamp, a worker id and a per-millisecond sequence into one sortable 64-bit number, and handle a clock that moves backwards.
In plain words
Make unique numbers without asking a central server: pack the time, a machine number and a small counter into one 64-bit number that sorts by creation time.
Why it matters
Auto-increment ids need one database to hand them out. This lets every server create ids on its own, keeps them roughly in time order, and is safe as long as machine numbers differ and the clock is handled.
Where you meet it
- Twitter's Snowflake and variants used by other social and chat products
- Ordering events and messages
- Primary keys in sharded databases
You will be able to
- Pack bits with BigInt
- Handle many ids in the same millisecond
- Explain what to do when the clock moves backwards
In interviews
The usual answer to 'design a unique ID generator'.
Practise on the Interview Ready roadmap1Learn the idea first
2See it work before you read the code
3Build it and run the check
What you build
- 41 bits of milliseconds since a custom epoch, 10 bits of worker id, 12 bits of sequence
- BigInt arithmetic for the packing
- A sequence counter that waits for the next millisecond when exhausted
- A guard for the clock going backwards
const EPOCH = 1_700_000_000_000n; // our own custom epoch (ms)
const WORKER_BITS = 10n, SEQ_BITS = 12n;
export class Snowflake {
private last = -1n;
private seq = 0n;
constructor(private worker: number, private now: () => number = Date.now) {
if (worker < 0 || worker >= 1 << Number(WORKER_BITS)) throw new RangeError("worker id out of range");
}
next(): bigint {
let t = BigInt(this.now());
if (t < this.last) throw new Error(`clock moved back by ${this.last - t} ms; refusing to issue ids`);
if (t === this.last) {
this.seq = (this.seq + 1n) & ((1n << SEQ_BITS) - 1n);
if (this.seq === 0n) { // 4096 ids in this millisecond: wait for the next one
while (t <= this.last) t = BigInt(this.now());
}
} else this.seq = 0n;
this.last = t;
return ((t - EPOCH) << (WORKER_BITS + SEQ_BITS)) | (BigInt(this.worker) << SEQ_BITS) | this.seq;
}
}
export function decode(id: bigint) {
return {
time: Number((id >> (WORKER_BITS + SEQ_BITS)) + EPOCH),
worker: Number((id >> SEQ_BITS) & ((1n << WORKER_BITS) - 1n)),
seq: Number(id & ((1n << SEQ_BITS) - 1n)),
};
}
Rather run it on your own computer?
Save the code as index.ts and the check as check.ts in one folder. The check exits with an error if anything is wrong and prints snowflake-id ok when everything passes. You need Node.js installed.
mkdir snowflake-id && cd snowflake-id
# save index.ts and check.ts in this folder
npx tsx check.tsHow real systems do it
This lab is the core idea in a small form. Here is what production versions add or do differently.
- Production hands out worker ids through a coordination service or a short lease, so two machines can never share one.
- When the clock jumps back, real generators either wait until it catches up or refuse to issue ids, as this lab does.
- 64-bit ids exceed what JavaScript numbers hold exactly, so APIs send them as strings. Newer standards such as UUIDv7 and ULID are also time-ordered and need no worker ids.
Ways to make unique ids
| Size and order | Needs coordination? | Weakness | Pick it when | |
|---|---|---|---|---|
| Snowflake (this lab) | 64 bits, sorted by time | Unique worker numbers | Depends on the clock not going backwards | High volume and you want ordered, compact ids |
| Database auto-increment | Small numbers, in order | Yes: one database hands them out | A single point of coordination | One database owns all the data |
| UUID v4 | 128 bits, random | No | Not sortable and hurts index locality | You only need uniqueness |
| UUID v7 or ULID | 128 bits, ordered by time | No | Larger than 64 bits | You want time order without worker numbers |
| Ticket server | Small numbers from one service | Yes: a central service | That service is a bottleneck | A simple, older setup |
What it cannot do
Worker numbers must be unique
Two machines with the same number can issue the same id. They need a registry or a lease.
The clock must not go backwards
After a time sync an id could repeat, so the generator refuses or waits.
Ids reveal when they were made
Anyone can decode the time, which can leak how fast you are growing.
The limits are fixed
41 bits of milliseconds lasts about 69 years from the epoch, and 12 bits allow 4,096 ids per worker per millisecond.
4Now extend it
Each one makes your version closer to what real systems do. Pick one and make the check cover it.
- Reserve datacenter bits inside the worker id
- Allocate worker ids safely with a lease
- Add a decoder that shows when an id was created