Published Updated
A UUID is 128 bits with a fixed textual shape and a version number baked into it. The versions are not interchangeable: they differ in how the bits are filled, and that difference decides whether your identifiers sort by creation time, whether they wreck your index, and what they reveal to whoever sees them.
What the 128 bits actually contain
Every UUID is 128 bits printed as 32 lowercase hexadecimal digits in a 8-4-4-4-12 grouping. Two small fields are fixed by the specification and are not free for the generator to use. The first digit of the third group is the version nibble. The first digit of the fourth group is the variant, which for every UUID you are likely to meet is one of 8, 9, a or b — those are the values whose top two bits are 10, marking the RFC layout as opposed to the older Microsoft GUID and NCS layouts.
That leaves 122 bits for the version to fill however it likes. Version 4 fills all of them with random data. Version 7 spends the first 48 on a millisecond timestamp and randomises the remaining 74. Version 1 spends 60 bits on a timestamp, 14 on a clock sequence and 48 on a node identifier. Versions 3 and 5 fill them from a hash. This is the entire substance of the version question. The governing document is RFC 9562, published in May 2024, which replaced RFC 4122 and standardised versions 6, 7 and 8 — so any material calling v4 the only sensible modern choice predates it.
Reading a UUID without a parser
You can read a UUID's version by eye, which is a useful skill when you are staring at a log line wondering why your identifiers will not sort. Count to the fourteenth hexadecimal digit — the first character after the second dash — and that is the version.
The example below is a real v7 value; its timestamp field decodes to an exact instant. Note that a v7 UUID only orders to the millisecond. Two generated within the same millisecond have no defined order unless the implementation uses the optional monotonic counter that RFC 9562 permits in the random field, which not all do. If you need a strict total order, a UUID is the wrong primitive — use a database sequence.
01a0c2c9-ef28-7c3a-9d41-6f2b8e57a104
^ ^
| +-- variant nibble: 9 (binary 10xx = RFC layout)
+------------ version nibble: 7
First 48 bits = 0x01a0c2c9ef28 = 1789974409000
milliseconds since the Unix epoch
= 2026-09-21T07:06:49.000Z
Compare a v4, where the same positions carry nothing:
9b2f4e1c-88d0-4a6b-b7e5-1c3f90ad2e44
^ ^
| +-- variant nibble: b
+-------- version nibble: 4
all 122 non-fixed bits are random; no time, no orderThe versions side by side
The table below is the decision in compressed form. Read the last column first: it is usually what settles the argument. Versions 2 and 8 are omitted because you will almost certainly never need them — v2 is a DCE Security variant that is effectively unused, and v8 is a deliberately empty slot for custom layouts that happen to want the UUID text shape.
One column needs a caveat. Sorting by time here means sorting by the raw bytes of the value, which is why v1 is marked as not sorting: its timestamp is stored least-significant field first, so two v1 values generated seconds apart can order arbitrarily.
| Version | How the bits are filled | Sorts by time | Use it when |
|---|---|---|---|
| v1 | 60-bit timestamp, 14-bit clock sequence, 48-bit node ID (historically the MAC address) | Not lexically — the timestamp is stored least-significant-first | You must interoperate with an existing v1 system. Do not choose it for new work. |
| v3 | MD5 hash of a namespace UUID plus a name | No | You need a deterministic ID and must match an existing v3 deployment. |
| v4 | 122 random bits from a CSPRNG | No | General-purpose identifiers where ordering does not matter and you want no time signal at all. |
| v5 | SHA-1 hash of a namespace UUID plus a name | No | The same input must always produce the same UUID. Preferred over v3 for new work. |
| v6 | v1 fields reordered so the timestamp is most-significant-first | Yes | You are migrating away from v1 and need its node/clock fields retained. |
| v7 | 48-bit Unix millisecond timestamp, then 74 random bits | Yes, to the millisecond | Database primary keys, event IDs, anything written in time order. The default choice for new systems. |
v4 and the collision question
The recurring worry about random identifiers is duplicates, and it is worth doing the arithmetic once so you can stop worrying. With 122 random bits the space holds roughly 5.3 × 10^36 values. Collision probability follows the birthday bound: it is approximately n² divided by twice the space size, where n is how many you have generated.
Generate a billion v4 UUIDs and the probability that any two collide is about 9 × 10^-20. Generate a hundred trillion — 10^14, more than any single system will produce — and it is about one in a billion. Reaching a coin-flip chance of a single collision takes roughly 2.7 × 10^18 UUIDs, which is what you get by generating a billion per second for a century.
The important caveat is that this arithmetic assumes a cryptographically secure random source. It is entirely possible to break v4 by generating it from Math.random, from a poorly seeded PRNG in a container that starts with no entropy, or from a fixed seed in a test harness that then leaks into production. When v4 collisions are reported in the wild, the cause is essentially always the generator, not the birthday bound. Use the platform CSPRNG: crypto.randomUUID in browsers and Node, secrets or uuid4 in Python, crypto/rand in Go.
Why random primary keys hurt, and what v7 fixes
This is the practical reason v7 exists. Most relational databases store table data or indexes in a B-tree ordered by key. Inserting in ascending key order means every new row lands in the rightmost leaf page, which is already in memory and fills up neatly before a new page is allocated. Inserting in random key order means every insert targets an arbitrary page, so the working set of dirty pages is the whole index rather than its tail.
A v7 key restores the ascending pattern while keeping 74 random bits, which is enough that identifiers remain unguessable in any practical sense. You also get an index that is naturally ordered by creation time, so range queries over a recent window touch a contiguous region instead of the whole tree.
Two storage details matter more than the version choice. Store UUIDs as 16 raw bytes — BINARY(16), uuid, RAW(16) — not as a 36-character string, which more than doubles the key width and multiplies through every secondary index. And if you are on MySQL, check how your driver orders the bytes: MySQL's own UUID_TO_BIN(uuid, 1) swaps the v1 time fields to make them sortable, which is correct for v1 and wrong for v7, where the bytes are already in the right order.
What a UUID tells the person who sees it
Version 1 is the one with a genuine history of surprises. Its node field was originally specified as the machine's MAC address, and many implementations still use one, so a v1 UUID can identify the physical machine that generated it and the moment it did so to within 100 nanoseconds. Modern libraries often substitute a random node, but you cannot tell which by looking. Treat any v1 value from an unknown source as carrying a timestamp and possibly a hardware address.
Version 7 discloses a creation timestamp, deliberately and precisely. This is usually harmless — the object almost always has a created_at column anyway — but think twice before using v7 for anything where the creation time is itself the secret: an unlisted share link, a password reset token, a pre-announcement record. It also makes identifiers weakly enumerable in time, so anyone who has two of your IDs knows roughly how many were issued between them only if you leak the counter, which v7 does not, but they do know the interval.
Versions 3 and 5 disclose nothing directly, but they are deterministic, which means anyone who can guess the input and knows the namespace can compute the UUID. uuid5(NAMESPACE_DNS, "example.com") is always cfbff0d1-9375-5685-968c-48ce8b15ae17, for everyone, forever. That is exactly what makes them useful for deduplication and idempotency keys, and exactly why they must never be used as capability tokens over a low-entropy input such as an email address or a sequential ID.
Choosing, and what a generator can and cannot do for you
The short version: use v7 for anything stored in a database or ordered by time, v4 when you specifically want no time signal, v5 when the same input must always map to the same identifier, and v1 only when an existing system forces your hand.
Our UUID generator produces values in the browser from the platform's cryptographic random source and sends nothing anywhere. Its limits are worth stating: a generated identifier is unique only in the sense of the arithmetic above, and uniqueness inside your data is still enforced by a unique constraint rather than by the format.
Finally, do not treat a UUID as an access-control mechanism. An unguessable identifier is a reasonable second layer, but a URL containing a v4 UUID is still handed out in referrer headers, pasted into chat, indexed if the page is public, and retained in server logs. Authorise the request, then use the UUID to look up the row.
What to remember
- Default to v7 for database keys and anything written in time order; it sorts to the millisecond and still carries 74 random bits.
- Use v4 when you want no time signal, v5 when the same input must always produce the same identifier, and v1 only for compatibility.
- v4 collisions in practice come from a weak random source, not from the birthday bound — always generate from the platform CSPRNG.
- Store UUIDs as 16 raw bytes rather than 36-character strings, and check your driver's byte ordering before applying a v1-era swap to a v7 value.
- Assume anyone holding a v1 UUID can read its creation time and possibly a MAC address, and anyone holding a v7 can read its creation time.