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 (standardised 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