Comparing All Seven Approaches
In one line: the scorecard is the artifact to carry out of this chapter. Being able to reproduce it — and explain why each cell is what it is — is the whole deliverable.
The complete scorecard
| Approach | Unique | Scalable | Available | 64-bit numeric ID | Causality maintained |
|---|---|---|---|---|---|
| Using UUID | |||||
| Using a database | |||||
| Using a range handler | |||||
| Using UNIX time stamps | weak | weak | |||
| Using Twitter Snowflake | weak | ||||
| Using vector clocks | weak | can exceed | |||
| Using TrueTime |
How to read the progression
Each row fixes the previous row's failure and reveals a new one:
- UUID — no coordination, but too big and only probabilistically unique.
- Database — compact and truly unique, but a SPOF, and increment-by-m breaks on membership change.
- Range handler — fixes all four, and carries no ordering information.
- UNIX timestamps — adds ordering, but caps at 86.4M/day and collides within a millisecond.
- Snowflake — fixes both by spending bits on worker and sequence; ordering is only as good as the clocks.
- Vector clocks — true causality without clocks, but O(n) in cluster size.
- TrueTime — everything, if you can afford GPS and atomic clocks.
That narrative is the answer to "design a unique ID generator." Walking it beats naming any single approach.
Choosing in practice
| Situation | Choose | Why |
|---|---|---|
| No size constraint, want zero coordination | UUID | Nothing to depend on; a partitioned server still generates valid IDs |
| Need compact unique keys, ordering irrelevant | Range handler | All four requirements, amortized coordination |
| Need compact, unique, and roughly time-sortable | Snowflake | The practical default — good enough ordering at no coordination cost |
| Need provable causality in a small cluster | Vector clocks | Only approach that detects concurrency without special hardware |
| Need strict external consistency, have the budget | TrueTime | Bounded, explicit uncertainty |
For most systems the answer is Snowflake or a Snowflake variant. It is the design that trades a weak causality guarantee — good enough for feeds, tracing, and last-write-wins — for compactness, throughput, and zero coordination.
Two things to do with the ID once you have it
Everything so far produces a number. Two follow-on decisions come up constantly and are separate from how the number was generated.
Encoding: making it short and URL-safe
A 64-bit integer printed in decimal is up to 20 characters, and it is not something you want in a URL. Re-express it in a larger alphabet and it shrinks:
base 10 : 0-9 -> 1,000,000,000 = 10 chars base 62 : 0-9 a-z A-Z -> 15FTGg = 6 chars
Base62 is the usual choice because those 62 characters are exactly the ones that are URL-safe without escaping and unambiguous in a path segment. Base64 is denser but includes +, / and =, which need encoding in a URL and defeat the purpose.
Two things worth knowing. The encoding is lossless and reversible, so the short string decodes back to the original integer — which means a lookup can go straight to a primary key rather than through a secondary index. And it is not compression: the information content is identical, you are only choosing a denser alphabet to write it in.
Some alphabets deliberately drop visually confusable characters — 0/O, 1/l/I — trading a little density for codes people can read aloud or type from a screenshot. Worth mentioning if the identifier will ever be spoken or transcribed.
Obfuscation: keeping sequential IDs from being enumerable
Earlier in this chapter the leak was named: sequential IDs let anyone estimate volume, and let an attacker walk the space. The instinct is to add random bits, which works and costs you sortability and length.
There is a better answer when the ID is a counter, and it is worth having ready because most candidates do not:
Because the transform is a bijection, uniqueness is preserved exactly — every counter value still maps to exactly one code, and there are no collisions to check for. You keep the compactness of a counter and lose the predictability.
What it does not do is hide the creation order from anyone who holds the secret, and it is obfuscation rather than authorization — a scrambled ID is not an access-control mechanism, and the object behind it still needs a permission check. But as a defence against enumeration and volume-estimation it is strictly better than padding with randomness, because it costs no extra bits.
The trade against sortability remains, and it is the honest reason not to always do this: scrambled IDs no longer sort by creation time, so if you were relying on the ID for ordering you have to give that up or carry a separate timestamp.
Six lessons that generalize
| Lesson | Detail |
|---|---|
| Duplicates are dangerous | Duplicate identifiers can cause critical errors — duplicate payments, for instance |
| Probabilistic vs deterministic | UUIDs offer probabilistic uniqueness; deterministic uniqueness often requires slower consensus mechanisms |
| Size costs throughput | Large keys slow down database updates; identifiers should be compact |
| IDs leak information | Random bits prevent external parties from guessing business metrics such as order volume |
| Counters aren't free | Simple counters are faster than timestamps but require persistent storage, creating bottlenecks and SPOFs |
| Sortable IDs create hotspots | Monotonically increasing IDs create hotspots in distributed databases like Spanner, reducing performance |
The cost of strict global ordering
Almost nothing needs a global order. Per user, per conversation, per account is nearly always the real requirement — and it is dramatically cheaper.
Enforcing a strict global order on events is costly in distributed systems. Centralized auto-increment is simple on one node but requires distributed consensus across many, which increases latency and coordination overhead.
Concretely: Google Spanner reports that single-row read-modify-write transactions can limit sequence generation to approximately 100 operations per second.
Read that against the requirement of one billion per day — roughly 11,600 per second. A strictly ordered global counter is two orders of magnitude short of the target.
Relaxing strict global ordering constraints can significantly improve throughput and reduce coordination costs. That is the sentence to remember: the reason Snowflake wins in practice is that it gives up perfect ordering to escape consensus entirely.
Key takeaway
Every column in the scorecard trades against another: size against causality, ordering against hotspots, determinism against coordination. The design skill is knowing which guarantee your system actually needs and refusing to pay for the rest. Two decisions follow generation and are separate from it: base62 encoding shrinks a 64-bit integer to a short URL-safe string losslessly and reversibly, and a reversible keyed transform (XOR or a small Feistel network) defeats enumeration while preserving uniqueness exactly — costing no extra bits, unlike padding with randomness, but giving up sortability.
Interview signal by level
| Level | What a strong answer sounds like |
|---|---|
| L4 | Names one approach and stops. |
| L5 | Compares a few: "UUIDs are too big, a central database is a SPOF, so Snowflake — compact and time-sortable." |
| Staff+ | Walks the progression and prices the guarantee: "each approach fixes the last one's failure. Snowflake is the practical answer because strict global ordering needs consensus, and Spanner reports around 100 ops/sec for that — two orders of magnitude below what we need. So I'd take weak ordering deliberately." |
Next: the whole thing under interview conditions.