UUID v4 or v7? The 128-bit layout and the real collision odds
Where the version and variant bits sit in a UUID, what v4's collision risk actually is, and why time-ordered v7 is kinder to database indexes, based on RFC 9562.
If your order page lives at /orders/1043, somebody will change it to 1044. With proper access checks nothing happens, but you’ve still told the world that the shop has taken a little over a thousand orders. Merging two databases is worse: both of them have an order 1043.
So you switch the IDs to UUIDs, something like d25c11b5-3995-48e8-8c0f-8be9a1550bcc. The next question is which kind. These days it’s usually a choice between v4 and v7, and the difference lives in the first twelve hex digits.
Two digits that aren’t random
A UUID is a 128-bit value written as 32 hex digits, hyphenated 8-4-4-4-12. Two positions carry fixed meaning:
- The first digit of the third group is the version. The
4in48e8says “this is v4”. - The first digit of the fourth group holds the variant bits. For every UUID in use today its top two bits are
10, so that digit is always8,9,aorb.
That’s enough to identify any UUID at a glance. If the third group of an ID in your logs starts with 7, it’s a v7.
v4: a 122-bit die
A v4 UUID fills all 122 bits not used by the version and variant with random data. That is 2¹²², about 5.3×10³⁶, possible values.
Will two ever collide? The birthday problem answers that. Draw k values at random from N possibilities and the chance of at least one repeat is about k²/(2N). Worked out:
| UUIDs generated | Chance of any collision |
|---|---|
| one billion (10⁹) | about 9.4×10⁻²⁰ |
| one trillion (10¹²) | about 9.4×10⁻¹⁴ |
| about 2.7×10¹⁸ | about 50% |
Reaching the last row takes a billion UUIDs a second for 86 years. The collisions that happen in practice come from bad randomness, not from the math: UUIDs faked with Math.random(), or cloned virtual machines that start with the same random-generator state. crypto.randomUUID() in browsers and Node.js builds v4 from a cryptographic generator, which takes that worry off the table.
The cost of being random: indexes
The weakness of v4 isn’t collisions, it’s that the values have no order. The default index in PostgreSQL and MySQL (InnoDB) is a B-tree, which keeps keys sorted across pages. An auto-increment ID always lands at the far right end, so only the last page is busy.
A v4 value lands anywhere. Once a table has tens of millions of rows, each insert is increasingly likely to need a page that isn’t in memory, and full pages split all over the index. Writes slow down and the index ends up half empty. MySQL’s InnoDB feels this most, because it stores the table itself in primary-key order.
v7: a timestamp up front
RFC 9562, published in May 2024, replaced RFC 4122 and added versions 6, 7 and 8. Version 7 is the one that fixes the problem above.
A v7 UUID starts with a 48-bit Unix timestamp in milliseconds, followed by 4 version bits, 12 random bits, 2 variant bits and 62 random bits. That leaves 74 random bits, fewer than v4, but they only need to be unique within the same millisecond.
Take 01a0e5f4-7bfb-772b-9d99-1c22f0eea280. Its first twelve digits, 01a0e5f47bfb, are 1790564400123 in decimal, which is 2026-09-28 03:00:00.123 UTC. Later UUIDs are larger, so sorting them as strings sorts them by creation time, and in an index they pile up at the right end like auto-increment IDs.
48 bits of milliseconds last until the year 10889, so running out isn’t a concern. What you do give up is privacy about timing: the creation time is readable from the ID. Whether it’s fine for anyone to see when a user signed up is a call each product has to make.
What happened to v1 and v6
Version 1 combines a count of 100-nanosecond intervals since 15 October 1582 with the machine’s MAC address. The MAC address identifies the machine that made the ID, and the time bits are stored out of order, so v1 values don’t sort by time. Version 6 is v1 with the time bits put in order, meant for systems already built on v1. For new work, RFC 9562 itself says to prefer v7 over v1 and v6.
Choosing
| Auto-increment | UUID v4 | UUID v7 | |
|---|---|---|---|
| Size | 4–8 bytes | 16 bytes | 16 bytes |
| Guess the next one | Easy | No | Time part yes, rest no |
| Generate in many places | Needs a central counter | Yes | Yes |
| B-tree inserts | At the end | Anywhere | Almost at the end |
| Reveals creation time | Only the order | No | To the millisecond |
For a database primary key, v7 is the sensible default. PostgreSQL 18 ships a uuidv7() function and Python 3.14 has uuid.uuid7(). For values where the timestamp would leak something, such as share-link tokens, v4 is better, and if unguessability is the whole point, a longer random token is worth considering. Either way, a UUID is not an access check.
The UUID generator makes v4 and v7 in bulk from your browser’s cryptographic random numbers. Generate a handful of v7s and watch the first digits stay the same while the tail changes.
References
- RFC 9562, Universally Unique IDentifiers (UUIDs) (2024)
- RFC 4122, A Universally Unique IDentifier (UUID) URN Namespace (obsoleted by RFC 9562)
- W3C Web Cryptography API,
Crypto.randomUUID() - PostgreSQL 18 documentation, UUID Functions; Python 3.14 documentation,
uuidmodule