A UUID (universally unique identifier) is a 128-bit label you can generate anywhere — in a browser, on a phone, inside a database — with no coordination, and still be practically certain it has never been used before. But "a UUID" is not one thing: the standard defines several versions that fill those 128 bits differently, and choosing between v4 and v7 genuinely matters once UUIDs become primary keys.
Anatomy of a UUID
The text form is 32 hexadecimal digits shown in five hyphen-separated groups of 8-4-4-4-12 — 36 characters in total, like 3f6b2c8e-9d1a-4f7b-8c2d-5e0a1b4c6d8f. The hyphens are pure formatting: the identifier is the 128 bits, also written as a plain 32-hex-digit string or a 16-byte binary value.
Two small regions carry metadata:
- Version nibble — the first hex digit of the third group (13th digit overall). In the example above it is 4. It tells you how the rest of the bits were produced: 1, 3, 4, 5, 7 or 8.
- Variant bits — the top bits of the first hex digit of the fourth group (17th digit). Modern UUIDs use the RFC 4122 variant, binary 10xx, so that digit is always 8, 9, a or b.
That leaves 122 bits of payload — and what those bits contain is what distinguishes one version from another.
v1 vs v4 vs v5 vs v7 compared
| v1 | v4 | v5 | v7 | |
|---|---|---|---|---|
| Source of bits | Timestamp + node (usually MAC address) | Random (122 bits) | SHA-1 hash of namespace + name | Unix timestamp (48 bits, ms) + random |
| Sortable by creation time? | No — the timestamp's low bits come first, so it does not sort cleanly | No | No | Yes — the timestamp is in the most significant bits |
| Privacy | Poor: leaks creation time and often the generating machine's MAC address | Excellent: reveals nothing | Good: reveals nothing, but same input always gives the same UUID | Moderate: leaks generation time, not the machine |
| Deterministic? | No | No | Yes — same namespace and name, same UUID | No |
| Standard | RFC 4122 (2005) | RFC 4122 | RFC 4122 | RFC 9562 (2024) |
v5 deserves a note of its own: it hashes a namespace UUID plus a name with SHA-1 and keeps 128 bits. Because it is deterministic, two systems that agree on a namespace derive identical IDs for the same item — useful for idempotent imports. Standard namespaces include DNS (6ba7b810-9dad-11d1-80b4-00c04fd430c8) and URL (6ba7b811-9dad-11d1-80b4-00c04fd430c8). The older v3 does the same with MD5; prefer v5. v7 arrived with RFC 9562 in 2024, which obsoleted RFC 4122.
v4 collision probability: the birthday math
A v4 UUID has 122 random bits, giving 2^122 ≈ 5.3 × 10^36 possible values. Collision fear usually cites this number, but the birthday problem is what matters: the chance of at least one duplicate grows with the square of how many you generate. Solving for a 50% collision probability gives roughly 2.71 quintillion (2.71 × 10^18) UUIDs.
To make that concrete: one billion UUIDs per second, non-stop, would take about 86 years to reach even odds of a single collision. At a million IDs over an application's lifetime, the duplicate probability is on the order of 10^-25. Two conditions apply: the randomness must be cryptographically strong (use crypto.randomUUID(), never Math.random()), and "practically impossible" is not "guaranteed" — if a collision would be catastrophic, enforce a unique constraint anyway.
Why v7 beats v4 for database primary keys
Random v4 IDs and B-tree indexes mix badly. A B-tree primary-key index (the default in PostgreSQL, MySQL InnoDB and most others) stores rows in key order. Purely random keys mean every insert lands at a random position: pages split constantly, cache locality vanishes, and indexes bloat. Sequential keys (auto-increment integers) insert at the rightmost edge, cheap and compact — but they leak record counts and require central coordination.
v7 splits the difference. Its first 48 bits are a Unix epoch timestamp in milliseconds, so fresh IDs sort after older ones and inserts still append near the right edge of the index, restoring locality. The remaining 74 random bits keep IDs unguessable and coordination-free. Sorting a v7 column also sorts by creation time, which can remove the need for a separate created_at index. PostgreSQL 18 ships a native uuidv7() function, and every major language has a mature library.
NIL and other special UUIDs
Two values are reserved rather than generated:
- NIL UUID:
00000000-0000-0000-0000-000000000000. All 128 bits zero. Use it as a null-object placeholder — "no UUID here yet" — instead of overloading NULL or inventing sentinel strings. - Max UUID:
ffffffff-ffff-ffff-ffff-ffffffffffff. All bits one, defined by RFC 9562. It sorts after every other UUID, handy as an exclusive upper bound in range queries.
Which version should you choose?
| Situation | Pick |
|---|---|
| General-purpose IDs: sessions, API resources, file names, correlation IDs | v4 |
| Database primary keys at any real insert volume | v7 |
| Same input must always produce the same ID (idempotent sync, content addressing) | v5 |
| Legacy systems that already expect it | v1 (avoid in new designs — leaks MAC and time) |
| Placeholder for "no identifier" | NIL |
The short version: v4 for plain uniqueness, v7 when the ID is indexed and inserted often, v5 when you need reproducibility. Need IDs right now? The UUID Generator creates cryptographically random v4 UUIDs in bulk — with uppercase or hyphenless formatting — entirely in your browser, so nothing you generate is ever sent anywhere.