What a good ID system needsCore idea
Option A: auto-increment (one database)Compare
Works while you have one database. Beyond that, replicas and multiple primaries fight over the same counter or hand out duplicates. Rule it out explicitly for a distributed system, and say why.
Option B: UUIDCompare
Generate a random 128-bit ID anywhere, with practically zero chance of collision.
Pros
No coordination, unique everywhere, hides volume, trivial to implement.
Cons
128 bits (twice as wide). Random order scatters inserts across a B-tree index, causing fragmentation and cache misses. No time order, which can matter for audit trails.
Option C: Redis INCR / ticket serverCompare
One Redis counter hands out IDs with INCR. Simple and fast, but Redis becomes a single point of failure unless replicated: if it is down, no IDs. Grabbing a range of IDs at once and handing them out locally removes the round trip per ID.
-- INCRBY returns the counter AFTER adding, i.e. the end of the block
local last = redis.call('INCRBY', KEYS[1], 1000)
local first = last - 999
return { first, last }Option D: Snowflake (the FAANG favourite)Pattern
A 64-bit ID built from three parts: a timestamp, a machine ID and a per-millisecond sequence number. Different machines and different milliseconds can never produce the same ID, so no coordination is needed.
const EPOCH_MS = 1_700_000_000_000; // a custom start date
class Snowflake {
private lastMs = -1;
private seq = 0;
constructor(private machineId: number) {} // 0..1023, from a config service
next(): bigint {
let now = Date.now() - EPOCH_MS;
if (now === this.lastMs) {
this.seq = (this.seq + 1) & 4095; // 12-bit sequence
if (this.seq === 0) { // used up this millisecond
while (now <= this.lastMs) now = Date.now() - EPOCH_MS;
}
} else {
this.seq = 0; // new millisecond
}
this.lastMs = now;
return (BigInt(now) << 22n) | (BigInt(this.machineId) << 12n) | BigInt(this.seq);
}
}Time-ordered
Sorting by ID sorts by creation time, which keeps indexes compact.
~69 years of range
41 bits of milliseconds from your chosen epoch.
4,096 IDs/ms/machine
About 4 million per second per worker.
No coordination
Each node only needs a unique machine ID. The bit split is tunable.
Option E: base62 short codesPattern
For user-facing links you want short IDs, so convert a Snowflake or UUID to base62 (digits plus lowercase and uppercase letters). This is exactly how the URL shortener tutorial turns a 64-bit ID into a 7-character code.
Choosing: decision tableSummary
| If you need… | Choose |
|---|---|
| A single small database | Auto-increment |
| Offline generation, zero coordination | UUID v4 |
| Distributed, ordered, index-friendly | Snowflake |
| A compact code users will see | Snowflake + base62 |
Common interview mistakesPitfalls
In the interviewIn the interview
Frequently asked questionsFAQ
How collision-safe is snowflake across a fleet?
It is guaranteed unique as long as machine IDs are unique and the per-millisecond sequence never wraps. Clock skew is the real danger, which is why production snowflake implementations either wait for the clock to catch up or, on large jumps, reject requests until sync.
Is a snowflake ID safe to leak in URLs?
The timestamp bits do leak creation order and approximate write rate, which is why public-facing identifiers such as payment references sometimes get a random prefix or a base62-obfuscated encoding. For internal primary keys that same ordering is usually a feature, because it keeps index inserts local.
What is a hardware counter alternative to snowflake?
For uniqueness without ordering, hardware random and cryptographic generators back UUIDv4 and UUIDv7 implementations. For ordered IDs, systems lean on database-native monotonic counters such as PostgreSQL sequences or TiDB auto-increment replicated through raft. Snowflake remains the interview standard because it needs no extra infrastructure.

Now explain id generation to me out loud
I'll give you a design problem and push on the trade-offs, like a real interviewer.

