Free preview

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

ApproachUniqueScalableAvailable64-bit numeric IDCausality maintained
Using UUID
Using a database
Using a range handler
Using UNIX time stampsweakweak
Using Twitter Snowflakeweak
Using vector clocksweakcan 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

SituationChooseWhy
No size constraint, want zero coordinationUUIDNothing to depend on; a partitioned server still generates valid IDs
Need compact unique keys, ordering irrelevantRange handlerAll four requirements, amortized coordination
Need compact, unique, and roughly time-sortableSnowflakeThe practical default — good enough ordering at no coordination cost
Need provable causality in a small clusterVector clocksOnly approach that detects concurrency without special hardware
Need strict external consistency, have the budgetTrueTimeBounded, 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

LessonDetail
Duplicates are dangerousDuplicate identifiers can cause critical errors — duplicate payments, for instance
Probabilistic vs deterministicUUIDs offer probabilistic uniqueness; deterministic uniqueness often requires slower consensus mechanisms
Size costs throughputLarge keys slow down database updates; identifiers should be compact
IDs leak informationRandom bits prevent external parties from guessing business metrics such as order volume
Counters aren't freeSimple counters are faster than timestamps but require persistent storage, creating bottlenecks and SPOFs
Sortable IDs create hotspotsMonotonically 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

LevelWhat a strong answer sounds like
L4Names one approach and stops.
L5Compares 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.

Enjoying the preview?

Create a free account to unlock the rest of this course, the in-browser judge, and live AI mock interviews.

Sign up free to continue