A UUID is 128 bits, written as 32 hexadecimal digits in five hyphen-separated groups of 8-4-4-4-12. But not all 128 bits are free. Two fields are fixed by the specification:
| Field | Position | Value in version 4 |
|---|---|---|
| Version | First digit of the third group | Always 4 |
| Variant | First digit of the fourth group | Always 8, 9, a or b |
Four bits of version plus two bits of variant leaves 122 random bits, not 128. That is why every version 4 UUID you generate has a 4 in the same place — a useful sanity check when debugging, and a reminder that a UUID is a structured value rather than a random blob.
Collision risk follows the birthday problem, approximately n² ÷ (2 × 2^122):
| UUIDs generated | Probability of any collision |
|---|---|
| 1 billion | about 1 in 10^19 |
| 1 trillion | about 1 in 10^13 |
| 1 quadrillion | about 1 in 10 million |
| 2.3 × 10^18 | about 50% |
Put in the usual way: generating a billion UUIDs every second for around 85 years gets you to roughly even odds of a single duplicate. For any real application the random-chance risk is not worth modelling.
But that arithmetic has a precondition, and it is where real collisions come from. It assumes 122 bits of genuinely unpredictable randomness. When duplicate UUIDs do turn up in production — and they do — the cause is almost never the birthday bound. It is entropy:
Browsers now expose a correct implementation directly, and using it removes the question. If you are generating UUIDs anywhere else, the thing worth checking is the random source, not the collision maths.
| Version | Built from | Property |
|---|---|---|
| 1 | Timestamp plus network card address | Time-ordered but leaks the machine and creation time |
| 3 and 5 | Hash of a namespace plus a name | Deterministic — same input always gives the same UUID |
| 4 | Random | Unguessable, unordered |
| 7 | Millisecond timestamp plus random | Unguessable in its tail and sortable by creation time |
Version 1 carries a genuine privacy cost that is easy to overlook: the identifier embeds the hardware address of the machine that made it, along with the moment it was made. This is not theoretical — it was famously used in 1999 to trace the author of the Melissa virus through identifiers embedded in the documents. Version 1 UUIDs in a public API leak infrastructure details.
Versions 3 and 5 are underused. Because they are deterministic, hashing a namespace and a stable name produces the same UUID every time, which gives you idempotent identifiers without a lookup table — useful for de-duplicating imports where the source has a natural key but no identifier.
Versions 6, 7 and 8 were standardised in RFC 9562, which replaced the older RFC 4122. Version 7 is the one most projects now want, for the reason in the next section.
The old advice — that UUIDs are unique so you can safely use them as primary keys — is true about uniqueness and misleading about consequences.
A database index is a tree kept in sorted order. Sequential integers append to the end of it, touching one page repeatedly, which stays in memory. Random UUIDs insert at random positions, so every insert touches a different page:
| Effect | Sequential key | Random UUID key |
|---|---|---|
| Pages touched per insert | Usually one, hot in cache | A different one each time |
| Page splits | Rare | Frequent |
| Index fragmentation | Low | High |
| Working set to stay fast | Small | Approaches the whole index |
On low insert volumes this does not matter. On high-volume tables it produces exactly the kind of slowdown that appears months after launch and is hard to attribute.
Storage compounds it. A UUID is 16 bytes as binary. Stored as a 36-character text column — an extremely common mistake — it becomes 36 or 37 bytes, and that inflation is repeated in every secondary index and every foreign key referencing it:
| Representation | Bytes |
|---|---|
| 32-bit integer | 4 |
| 64-bit integer | 8 |
| UUID as binary | 16 |
| UUID as fixed-length text | 36 |
The fixes, in order of preference: use version 7 so identifiers are time-ordered and insert near the end of the index; store as the native UUID type or as 16 raw bytes rather than text; or keep a monotonic internal key for clustering and expose the UUID as a separate indexed column. Version 7 solves most of this with a one-line change and keeps the useful property that identifiers can be generated by the client without a round trip.
Version 4 UUIDs are unguessable, which tempts people into using them as bearer tokens — the unlisted link, the password-reset URL, the invitation. That works up to a point, and the point is worth knowing.
An identifier in a URL appears in server logs, in referrer headers sent to third parties, in browser history, in shared screenshots and in link previews generated by chat applications. It does not expire on its own, it cannot be scoped, and revoking it means invalidating the resource. For anything short-lived and low-value that is an acceptable trade; for account recovery it is not, and such tokens should be separately generated, hashed at rest, scoped and given a short expiry.
Version 1 and version 7 UUIDs are additionally partly predictable, since the timestamp portion is known, so neither should ever be used as a capability token.
UUIDs are canonically lowercase but comparison is case-insensitive, so a system that compares them as raw strings will fail against a source that emits uppercase. Microsoft-style GUIDs are often uppercase and sometimes wrapped in braces, both of which must be normalised away.
There is also a byte-order trap. The Microsoft binary representation stores the first three fields in little-endian order, while the specification defines them big-endian. The same GUID therefore has two different byte layouts, and moving binary values between a .NET system and a standards-compliant one without conversion produces identifiers that look scrambled — a real and frequently painful interop bug.
Finally, they are not human-friendly. Thirty-six characters cannot be read over a phone, quoted in a support ticket reliably, or typed without error. Where a person has to handle the identifier, a short human-readable code alongside the UUID is worth the extra column.
From chance alone, effectively never. From a broken random source, quite easily — which is the risk actually worth guarding against.
Version 7 for database identifiers, because of the index behaviour above. Version 4 where ordering would leak information you would rather not disclose, such as signup sequence or volume.
Only for low-value, short-lived things. It is unguessable but it is not confidential, because URLs travel further than people expect.
No. They are produced by your browser cryptographic random source and nothing is transmitted, logged or stored. Copy what you need before closing the page.
Every tool comes with a written guide, and every category is one click away.