← Back to Blogs
HN Story

When the 'Impossible' Happens: Deconstructing a UUID v4 Collision

May 10, 2026

When the 'Impossible' Happens: Deconstructing a UUID v4 Collision

In the world of software engineering, there are certain constants we treat as gospel. One of those is the belief that UUID v4—with its 122 bits of randomness—is effectively immune to collisions in any practical application. The math suggests that for a dataset of 15,000 records, the probability of a collision is approximately $2 \times 10^{-29}$, a number so infinitesimally small that it is often described as "fewer collisions per universe lifetime than atoms in your liver."

However, a recent report from a developer using the popular uuid npm package turned this theoretical certainty on its head. Despite having only 15,000 records, their system flagged a duplicate UUID v4. This event serves as a critical reminder: in production, "statistically impossible" is not the same as "technically impossible."

The Anatomy of a Collision

When a collision occurs in a space as vast as UUID v4, the immediate instinct is to assume a miracle or a cosmic ray. But as the technical community pointed out in the ensuing discussion, the culprit is almost never the math—it is the implementation.

The Entropy Problem

UUID v4 relies entirely on a high-quality source of randomness (entropy). If the Pseudo-Random Number Generator (PRNG) is poorly seeded or compromised, the effective search space collapses. Several likely candidates for this failure include:

  • Deterministic Environments: Some environments, such as Googlebot's JavaScript execution, use deterministic randomness. If UUIDs are generated on the client side, a bot crawling the site may generate the same "random" ID repeatedly.
  • Virtualization and Forking: In some VM or containerized environments, the entropy state can be duplicated during a process fork. If two child processes inherit the same PRNG state, they may produce identical sequences of "random" numbers.
  • Kernel-Level Bugs: Rare race conditions in the OS kernel (e.g., reading from /dev/random on multi-processor systems) can lead to different processes receiving the same byte sequences.
  • Library Fallbacks: Older versions of some libraries would fall back to Math.random() if crypto.getRandomValues() was unavailable. Math.random() is not cryptographically secure and has a significantly smaller period, increasing collision risks.

The "Human" Factor

Before blaming the PRNG, seasoned engineers suggest looking at the data pipeline. Most "UUID collisions" are actually data management errors:

  • Database Restores: Copying rows between environments or restoring a snapshot can introduce duplicates.
  • Retry Logic Bugs: A code path that generates a UUID, fails an insert, and retries using the same variable still in scope can look like a collision.
  • Manual Intervention: Manual database edits or CSV imports often bypass the uniqueness checks that the application logic relies on.

Collision-Resistant vs. Collision-Proof

One of the most poignant takeaways from this incident is the distinction between collision-resistant and collision-proof identifiers.

"Randomness guarantees fuck all. These IDs are collision-resistant not collision-proof."

If a system requires a absolute guarantee of uniqueness, randomness alone is insufficient. To move from "highly unlikely to collide" to "guaranteed not to collide," engineers can employ several strategies:

1. Database Enforcement

Regardless of how an ID is generated, the database should be the final arbiter of truth. Using a UNIQUE constraint ensures that a collision is caught at the moment of insertion, preventing data corruption. As one contributor noted, a collision should be a "log discovery," not a silent failure.

2. Hybrid Identifiers (UUID v7)

UUID v7 is gaining popularity because it incorporates a timestamp. By making the ID time-ordered, the collision space is restricted to IDs generated at the exact same millisecond. This not only reduces the probability of collision across the entire dataset but also significantly improves database indexing performance by reducing B-tree fragmentation.

3. The "Generate and Check" Loop

For high-assurance systems, the safest pattern is to wrap the ID generation in a loop:

  1. Generate a candidate ID.
  2. Check if it exists in the database/cache.
  3. If it exists, repeat step 1.
  4. Insert the unique ID.

Final Thoughts

While the odds of a natural UUID v4 collision are astronomical, the real world is messy. Hardware defects, software bugs, and environment quirks can turn a $10^{-29}$ probability into a production outage. The lesson is simple: never assume a property of your data based solely on the math of its generator. Build your systems to handle the "impossible," because at scale, the impossible becomes inevitable.

References

HN Stories

  • #48060054 Ask HN: We just had an actual UUID v4 collision... Discussion ↗