On Generating Unique IDs

Mohammed A.
Jul 12, 2023
On Generating Unique IDs

We reach for UUIDs as the default solution for generating unique identifiers. Need a primary key? UUID. Need a temporary token? UUID. Need to track something across systems? UUID.

In theory, theory and practice are the same. In practice, they are not.

— Albert Einstein

And why not? The 128-bit identifier with astronomically low collision probability, to put this into perspective, you could give each star in the observable universe a billion unique ID, and you would still be covered. Generate them anywhere, anytime, no coordination needed. It feels like a solved problem. No?

But here’s the truth: UUIDs will collapse at scale. The math is comforting until it isn’t, and the assumptions behind those guarantees break down in real-world systems—especially multi-core and distributed ones.

This article isn’t about whether UUIDs are “good enough” for most applications. They are. It is about understanding what happens when you outgrow the comfortable probabilistic assumptions and face the limitations of the physical world we live in.

The Math Is Comforting

Let’s look at the numbers. A UUID v4 has 122 random bits (and 6 bits are reserved for version and variant), giving us about 3.4 × 10^38 possible values. The collision probability is so low that generating 1 billion UUIDs per second for 85 years still leaves you with a negligible chance of collision.

To put it another way: you’re more likely to be struck by an asteroid than to see two random UUIDs collide.

This is why we feel safe. The math says collisions are virtually impossible, so we design our systems assuming they won’t happen. We treat UUID generation as a fire-and-forget operation—no coordination, no checks, just generate and move on.

Why UUIDs Collapse on Multi-Core Systems

The math assumes a perfect random number generator (RNG). But in practice, RNGs have flaws that become visible at scale.

On multi-core systems, you’ve got multiple threads generating UUIDs concurrently. Each thread is calling into the same random number generator, and that’s where things get messy. The PRNG (Pseudo-Random Number Generator) has internal state that gets mutated with each call. When multiple threads hit it simultaneously, you get race conditions.

Two threads might read the same state value, generate the same “random” number, and produce identical UUIDs. The collision probability isn’t theoretical anymore—it’s a timing bug.

Then there’s the entropy pool. Your system has a limited source of true randomness, and high-concurrency applications can exhaust it. When the entropy pool runs dry, the RNG either blocks or falls back to less random sources. Either way, you’re not getting the 122 bits of randomness you thought you were.

Why UUIDs Collapse in Distributed Systems

Multi-core is one problem. Distributed systems introduce entirely new failure modes.

Consider container and VM cloning. When you spin up multiple instances from the same snapshot, they might start with identical PRNG states. Two containers boot up, both generate UUIDs using the same seed, and you’ve got guaranteed collisions until their states diverge. This isn’t theoretical—it’s happened in production.

For time-based UUIDs (v1, v7 to-be), there’s the clock skew problems. In distributed systems, clocks drift. NTP syncs can move time backward. If your UUID generation depends on timestamps, you can generate the same ID twice or create IDs that appear out of order. The collision isn’t about randomness anymore—it’s about time being unreliable.

Then there’s the fundamental issue of coordination. The whole promise of UUIDs is that you don’t need coordination. But when the probabilistic guarantees break down, you’re left without a fallback. Distributed systems need coordination systems to handle the edge cases that UUIDs can’t.

So What Do Bigger Systems Do?

They do not rely on probabilistic uniqueness and they build deterministic systems.

Companies operating at scale have moved toward dedicated ID generation services. Twitter’s Snowflake generates 64-bit IDs using timestamp, machine ID, and sequence number. Instagram built a similar system. These services trade coordination for certainty—you get guaranteed uniqueness at the cost of infrastructure.

Others use coordination systems like Zookeeper or etcd to manage ID ranges, assign machine IDs, or synchronize state. The point is: at scale, you’re not avoiding coordination -> you’re choosing where to have it.

In Part 2, I’ll dive deeper into these approaches and explore how big tech companies have built it systems that doesn’t collapse when the math stops working in your favor.

Conclusion

UUIDs work for most applications. The collision probability is tiny, and for many systems, “good enough” is actually good enough.

But understand what you’re betting on. UUIDs give you probabilistic uniqueness, not absolute guarantees. The math assumes perfect randomness, perfect isolation, perfect timing—none of which exist in real systems.

At scale, probability becomes reality. Race conditions happen. Entropy pools exhaust. Clocks drift. Containers clone. The theoretical impossibility of collisions collides with the messy reality of distributed systems.

Design for the collapse, not the ideal case. Know where your assumptions break. Have a plan for when the math stops working in your favor—Because it will :D.

Glossary

RNG: Random Number Generator, a system that generates a sequence of random numbers.

PRNG: Pseudo-Random Number Generator, an algorithm that generates a sequence of numbers that appear random but are determined by an initial value called a seed.

Entropy Pool: A collection of random data gathered by an operating system from various sources (mouse movements, keyboard timings, hardware interrupts) used to generate truly random numbers. Learn more.

Race Condition: A situation where the behavior of a system depends on the relative timing of events, often leading to unexpected results when multiple threads access shared data concurrently.