Version 4: Random, With Six Fixed Bits
A UUIDv4 is 128 bits, of which 122 are random. Six are fixed: four encode the version (4)
and two encode the RFC 4122 variant. That is why every v4 UUID has a 4 at the start of the
third group and one of 8, 9, a or b at the start of the fourth:
``
9f1c8a2e-7b3d-4f6a-9c2e-1d8b4a6f3c07
↑ ↑
version variant
`
Checking those two positions is the cheapest way to tell a real v4 from a random hex string
in the right shape.
Collisions
With 122 random bits, the probability of a collision stays negligible far beyond any
realistic volume:
| UUIDs generated | Collision probability |
|---|---|
| 1 billion | ~1 in 10²¹ |
| 1 trillion | ~1 in 10¹⁵ |
| 10¹⁸ | ~1 in 10⁵ |
You would need to generate a billion per second for 85 years to reach a 50% chance of one
collision. In practice, collisions in production come from a broken random source, not from
the birthday bound — which is exactly why this generator uses crypto.getRandomValues()
rather than Math.random().The Database Cost
Random UUIDs as primary keys are hard on B-tree indexes. Each insert lands at a random point
in the index rather than at the end, which fragments pages, inflates the index and worsens
cache locality. On a large, write-heavy table the difference against a sequential key is
substantial.
| Key type | Insert locality | Sortable by time | Size |
|---|---|---|---|
| Auto-increment integer | Sequential | Yes | 4–8 bytes |
| UUIDv4 | Random | No | 16 bytes |
| UUIDv7 | Sequential | Yes | 16 bytes |
| ULID | Sequential | Yes | 16 bytes |
UUIDv7 puts a millisecond timestamp in the high bits, keeping the uniqueness and the
distributed generation while restoring insert locality. If you are choosing today for a new
table, it is usually the better default.Where v4 Is Exactly Right
Identifiers generated on clients or across services with no coordination- Idempotency keys for API requests
- Correlation and trace IDs
- Anything where a sequential key would leak volume or ordering
Not a Secret
A v4 UUID is unguessable, which tempts people to use one as a capability — an unlisted URL, a
password-reset token. It is 122 bits of randomness, so that is defensible in isolation, but
UUIDs end up in logs, analytics, referrer headers and browser history in a way that real
tokens are handled to avoid. Use a purpose-built token with an expiry for anything that
grants access.
The Avalanche Effect
A one-character change produces a completely different digest — not a similar one. That
property is what makes a hash useful as a fingerprint:
helloInput MD5 CRC32 hello 5d41402abc4b2a76b9719d911017c592 3610a686 hello. d94c10e437d18531e122ed0b45badd2a 0a39d4f1 Hello 8b1a9953c4611296a827abf8c47804d7 f7d18982 and Hello differ by one bit of one byte, and share no part of their output.
RIPEMD-160 of hello is 108f07b8382412612c048d07d13f814118445acd, and of Hello is
d44426aca8ae0a69cdbc4021c64fa5ad68ca32fe` — same story.
Digest Length and Collision Resistance
| Algorithm | Output | Birthday bound | Status |
|---|---|---|---|
| CRC32 | 32 bits | ~77,000 values | Checksum only |
| MD5 | 128 bits | 2⁶⁴ in theory | Broken — collisions in seconds |
| SHA-1 | 160 bits | 2⁸⁰ in theory | Broken — SHAttered, 2017 |
| RIPEMD-160 | 160 bits | 2⁸⁰ | No practical attack |
| SHA-256 | 256 bits | 2¹²⁸ | Current standard |
| SHA-512 | 512 bits | 2²⁵⁶ | Standard, faster on 64-bit |
Never Hash a Password With These
A general-purpose hash is designed to be fast, which is exactly wrong for passwords: speed helps the attacker. Use a deliberately slow KDF — bcrypt, scrypt or Argon2id — with a per-password salt. A GPU tries billions of SHA-256 guesses a second and a few thousand bcrypt guesses a second, and that gap is the entire defence.