UUID v4: what the 122 random bits buy you, and what they cost

122 random bits make collisions a non-issue, do not make a UUID a secret, and make it a poor primary key. The reasoning behind all three.

The anatomy

A UUID is 128 bits, conventionally written as 32 hex digits in a 8-4-4-4-12 grouping. In version 4, six of those bits are fixed (four identify the version and two the variant), leaving 122 bits of randomness.

You can read the version straight off the string: it is the first digit of the third group. …-4f8a-… is a v4. The UUID Generator produces v4, in bulk, with a toggle for dashes and case, both purely cosmetic, since the underlying value is the same 32 nibbles either way.

Collisions are genuinely not your problem

122 random bits is about 5.3 × 10³⁶ possible values. By the birthday bound you would need roughly 2.7 × 10¹⁸ UUIDs (2.7 quintillion) before reaching a 50% chance of a single collision anywhere. Generating a million per second, that is on the order of 85,000 years.

The realistic failure is not mathematics but a bad random source: a weak PRNG, a seed reused across forked processes, or an embedded device with no entropy at boot. Browsers expose crypto.randomUUID(), which draws from the platform CSPRNG; if you are generating UUIDs somewhere unusual, verify the source rather than the format.

A UUID is an identifier, not a secret

Unguessable and secret are different properties. A v4 from a good CSPRNG is genuinely unguessable, but identifiers get treated like identifiers: they land in URLs, logs, referrer headers, analytics payloads, support tickets and screenshots. Using one as a password-reset token or a share link means the secret leaks through every one of those channels.

If you need a bearer secret, generate a purpose-built one (the Random String or Password Generator tools), keep it out of URLs where you can, and store only a hash of it server-side. Then the identifier can stay boring and public.

Why v4 makes a poor primary key

This is the cost that shows up at scale. A v4 is random, so consecutive inserts scatter across the whole key space. On a clustered B-tree index (InnoDB's primary key, a SQL Server clustered index), that means writes hit random pages instead of appending to the end, causing page splits, fragmentation and a working set that no longer fits in cache. Sequential keys append; random keys thrash.

The secondary cost is size. 16 bytes stored as binary, 36 as a hyphenated string, and every secondary index carries a copy of the primary key. Storing UUIDs as CHAR(36) when a native uuid or BINARY(16) type exists more than doubles that overhead for nothing.

The time-ordered alternatives

If you want a UUID-shaped key without the write pattern, the answer is a time-ordered identifier: UUID v7 (standardized in RFC 9562) puts a millisecond timestamp in the high bits and randomness in the rest, so values sort roughly by creation time and inserts stay local. ULIDs solve the same problem with a different encoding.

This tool generates v4 only, which is the right default for a public identifier where ordering does not matter and leaking creation time would be unwelcome. Reach for v7 or a ULID at the database layer, and note the trade-off you are accepting: a time-ordered id tells anyone holding it approximately when the row was created.

One more version worth knowing about

If you encounter a v1 UUID, look closer: it encodes a timestamp and, classically, the generating machine's MAC address. That has been used to deanonymise document authors more than once. It is not a reason to panic over legacy data, but it is a reason not to choose v1 for anything new.

Tools used in this guide