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.

Intermediateabout 35 minTypeScript · no dependencies

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 roadmap

1Learn 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
Press “Run the check” to see it work. The check runs in your browser.
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.

Commands
mkdir snowflake-id && cd snowflake-id
# save index.ts and check.ts in this folder
npx tsx check.ts

How 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 orderNeeds coordination?WeaknessPick it when
Snowflake (this lab)64 bits, sorted by timeUnique worker numbersDepends on the clock not going backwardsHigh volume and you want ordered, compact ids
Database auto-incrementSmall numbers, in orderYes: one database hands them outA single point of coordinationOne database owns all the data
UUID v4128 bits, randomNoNot sortable and hurts index localityYou only need uniqueness
UUID v7 or ULID128 bits, ordered by timeNoLarger than 64 bitsYou want time order without worker numbers
Ticket serverSmall numbers from one serviceYes: a central serviceThat service is a bottleneckA 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
Saved to your account when you are signed in.

Check yourself

1 of 4. Why do these ids sort by creation time?