All posts

What UUIDs Actually Guarantee (and What They Don't)

4 min read

UUID collisions are astronomically unlikely until they happen. Here's when you can rely on them, which version to use, and what they don't protect against.

A team generated UUIDs in a microservice that restarted frequently. The random seed was tied to the startup time, and the generator produced the same sequence of IDs after every deploy. The collision rate was 100% within each batch of 1000 IDs generated after the same restart. Three days later, the database started throwing unique constraint violations.

The math backs the UUID guarantee: UUID v4 generates 122 random bits, and you'd need about 2.7 trillion IDs for a 50% collision chance. The practical risks come from implementation bugs, not probability. The story above is a real case of a custom generator that reused the same random seed on every request.

UUID v4: Most Common, Rarely the Problem

Use your framework's function, not a custom generator. Python's uuid module, Node's crypto.randomUUID(), and Java's java.util.UUID all handle randomness correctly. The failures happen when someone decides to "optimize" by seeding from the system clock or reusing a static seed.

The randomness is sufficient for virtually all use cases. The collision math doesn't change between languages or frameworks. What changes is whether the implementation seeds the RNG correctly. Some homegrown solutions skip the entropy pool entirely and use rand() seeded with time(NULL). Those produce collisions within hours at any meaningful scale.

UUID v7: Better for Databases

UUID v4 has a performance problem with databases. The values are random, so inserting them into a B-tree index causes page splits. Each new row goes to a random page instead of the end of the table. Over time, index fragmentation slows down inserts.

UUID v7 solves this by making the first 48 bits a timestamp. IDs generated close together sort sequentially, which maintains index performance.

UUID v4:  f47ac10b-58cc-4372-a567-0e02b2c3d479
UUID v7:  018f3a6e-1c8c-7a5b-8000-3a7c8d9e0f12

Drizzle ORM and Prisma both support UUID v7 generation. PostgreSQL handles v7 natively. MySQL still requires BINARY(16), a reminder that database UUID support is inconsistent even within the same spec version.

Storage Space Adds Up

UUIDs are 128-bit values. How you store them affects performance significantly.

Format Size Example
Text (VARCHAR(36)) 36 bytes f47ac10b-58cc-4372-a567-0e02b2c3d479
Binary (BINARY(16)) 16 bytes 16 raw bytes
PostgreSQL uuid type 16 bytes Native type

Most ORMs default to the text representation because it's human-readable. If your UUID column is the primary key and your table has millions of rows, switching from text to binary storage can cut index size by more than half. PostgreSQL's native uuid type handles this automatically. MySQL requires BINARY(16). The RFC 4122 spec defines the binary format, but each database implements it differently.

The size difference compounds in production: backups take longer, replication lags more, and fewer index pages fit in cache. A table with 50 million rows and a text UUID primary key uses roughly 1.8 GB for the PK index. Switching to binary cuts that to 800 MB. PostgreSQL's UUID documentation covers the storage layout.

What UUIDs Don't Do

UUIDs guarantee practical uniqueness. That's it. They don't guarantee much else, and treating them as a Swiss Army knife causes predictable problems.

  • Unpredictability. UUID v1 uses the MAC address and timestamp. If someone knows when you generated an ID, they can guess others around the same time. V4 and v7 are better, but don't use UUIDs as session tokens.
  • Security. A UUID in a URL is not access control. I've seen teams use random UUIDs as "private" share links assuming they can't be guessed. With 122 bits of entropy, enumeration is impractical, but that's obscurity, not security. If the resource needs actual protection, add authentication. The UUID generator on this site can generate one for you, but pair it with real authorization.
  • Ordering. UUID v4 sorting is meaningless. Don't rely on ORDER BY id for chronological order unless you're using v7. This mistake shows up regularly in admin panels and audit logs, where recent records aren't always at the end of the table.

Auto-increment comparison. Serial IDs beat UUIDs on insert speed and index size. The tradeoff is coordination: auto-increment needs a single writer or careful sequence management across instances. UUIDs let any node generate IDs independently. Pick based on your write topology, not habit.

For new projects, use v7 for database primary keys and v4 everywhere else. If you're migrating an existing system, benchmark first. The read patterns of your workload matter more than the version number.