On this page
UUID v4 and UUID v7 are both 128-bit identifiers, and that is where the similarity ends. v4 spends 122 bits on randomness. v7 puts a 48-bit Unix millisecond timestamp in front, then 4 version bits, 74 random bits, and 2 variant bits. The version number is not a ranking. It is a statement about what the identifier is allowed to leak and how it sorts.
Both are defined in RFC 9562, which replaced RFC 4122 in 2024. The practical choice comes down to three questions: does the identifier need to sort by creation time, will it land in a B-tree primary key, and is it acceptable for the creation time to be readable by anyone holding the value. This guide answers those with the byte layout rather than a rule of thumb.
The 128-bit layout, side by side
Every UUID is 16 bytes, and the fields are defined by position, not by the textual dashes. Byte 6 contains the version in its high nibble. Byte 8 contains the variant in its top two bits. Everything else depends on the version. In the canonical string, the 13th hexadecimal character is the version and the 17th is the variant.
For v4, the 122 bits that are not version or variant are random. For v7, the first 48 bits are the Unix timestamp in milliseconds, big-endian, which is why v7 values sort in creation order as raw bytes. The remaining 74 bits are random by default, though RFC 9562 allows some of them to carry a counter for monotonicity within a millisecond.
| Field | Bits | v4 | v7 |
|---|---|---|---|
| Bytes 0-5 | 48 | Random | unix_ts_ms, big-endian |
| Byte 6 high nibble | 4 | 0100 (version 4) | 0111 (version 7) |
| Byte 6 low nibble and byte 7 | 12 | Random | rand_a |
| Byte 8 top bits | 2 | 10x variant | 10x variant |
| Bytes 8-15 remaining | 62 | Random | rand_b |
| Total random bits | 122 | 122 | 74 |
v4 in detail
A v4 UUID is a random number with six fixed bits. The high nibble of byte 6 is 0100, so the 13th character of the canonical string is always 4. The top two bits of byte 8 are 10, so the 17th character is always 8, 9, a, or b. Those are the only predictable characters; the other 30 hexadecimal digits are random.
The randomness is the security property. A v4 value carries no timestamp, no sequence, and no machine identifier, so it reveals nothing about when or where it was created. That makes it a reasonable choice for a public identifier that must not be enumerable, provided it is generated by a cryptographically secure random source. A 122-bit space makes collisions negligible: a billion values give a collision probability of roughly 2^-63.
- Version nibble: always 4 at position 13 of the canonical string
- Variant: always 8, 9, a, or b at position 17
- Random bits: 122, from a CSPRNG
- Sorts by nothing meaningful; order is random
- Reveals no creation time and no sequence
v7 in detail
A v7 UUID is time-ordered. Bytes 0 through 5 hold the number of milliseconds since the Unix epoch, big-endian. The high nibble of byte 6 is 0111. The low nibble of byte 6 and all of byte 7 form rand_a, the top two bits of byte 8 are the variant, and the remaining 62 bits are rand_b. That is 74 random bits plus a timestamp that is readable by inspection.
The timestamp is 48 bits, which is enough for milliseconds until the year 10889, so overflow is not a practical concern. Two v7 values created in the same millisecond differ only in their random or counter bits, and a value created later always compares greater as a byte string than one created earlier, as long as the clock does not go backwards. That ordering is the whole point.
| Byte | Content | Example |
|---|---|---|
| 0-5 | Unix milliseconds, big-endian | 0192f3c4a1b2 |
| 6 high nibble | Version 7 | 7 |
| 6 low nibble + 7 | rand_a, 12 bits | a1b |
| 8 top 2 bits | Variant 10 | 8, 9, a, or b |
| 8 (low 6) to 15 | rand_b, 62 bits | Random or counter |
Sorting and monotonicity within a millisecond
Lexicographic sorting of the canonical text matches byte order, because the hyphens sit at fixed positions and hexadecimal digits sort in the same order as their values when lowercase. Sort by the text form or by the 16 bytes and you get creation order. That holds across machines without coordination, which is why v7 is attractive for event logs and for identifiers that become part of a sort key.
The gap is within a single millisecond. By default, two values created in the same millisecond are ordered by their random bits, which is not creation order. RFC 9562 describes using the rand_a field, or the leftmost random bits, as a counter seeded randomly at each timestamp tick. With that method, values are monotonic for practical purposes within a millisecond, at the cost of a little less randomness and a shared counter that must be handled carefully by concurrent producers.
- Across milliseconds: ordering is guaranteed while the clock is monotonic
- Within a millisecond: random order unless a counter is used
- Counter method: seed randomly per tick, increment per value
- Clock rollback: can break ordering, so use a monotonic clock source
- Concurrency: increment the counter atomically or lose the property
Index cost in a B-tree
A random v4 primary key inserts at a random position in the index. On a B-tree that means touching a different leaf page on almost every insert, evicting pages from the buffer pool, and splitting pages in the middle, which leaves them half full. The index grows larger, the write amplification rises, and cache hit rates fall as the table grows.
A v7 key inserts near the right edge, so the working set stays small and page splits happen at the end where they are cheap. The trade-off is contention: every writer wants the same rightmost page. On a single database that is usually fine with a few hundred writes per second; at very high concurrency it becomes a hotspot, and the usual mitigations are batching, connection-level interleaving, or a small amount of jitter in the high bits.
| Property | Random v4 key | Time-ordered v7 key |
|---|---|---|
| Insert location | Random leaf page | Rightmost leaf page |
| Page splits | Frequent, mid-page | Rare, at the end |
| Buffer pool locality | Poor on large indexes | Good |
| Index size | Larger, lower fill factor | Compact |
| Write hotspot | Spread across the tree | Concentrated on the right edge |
| Range scan by time | Requires a separate column | Native |
Privacy and the timestamp leak
A v7 value tells anyone who holds it roughly when it was created, to the millisecond, and therefore how many other records were created around that time. That is fine for internal keys and event ids. It is not fine for a public identifier that should not reveal signup volume, order rate, or the age of an account.
If you need both ordering and opacity, the options are to keep v7 internal and expose a separate random public id, or to use v4 for anything that leaves your system. Do not treat a UUID as a capability token in either version; both are identifiers, not authorization. A v4 id is unguessable in practice, but it still must be checked against access control on every request.
- v7 reveals creation time to the millisecond
- Enumerating nearby v7 values reveals record volume
- Keep v7 internal, expose v4 or a separate public id
- Neither version is an access token or a secret
- Randomized v7 is not a thing; the timestamp is mandatory
Storage and encoding size
Sixteen bytes is the storage answer for both versions. The canonical text form is 36 characters, of which 32 are hexadecimal digits and 4 are hyphens. Storing the text form doubles the size and slows comparisons, so use a native UUID type where one exists, BINARY(16) where one does not, and format only at the boundary.
SQL Server has a wrinkle worth testing: its uniqueidentifier type sorts in a byte order that is not the same as the canonical string, and the internal byte order differs from RFC 4122. Code that relies on v7 ordering must verify the sort behaviour on that platform rather than assuming it. Where you need a compact text form, base64url without padding gives 22 characters, but it is not a standard UUID representation and will confuse tooling that expects the canonical form.
| Representation | Size | Notes |
|---|---|---|
| Binary | 16 bytes | Native storage, fastest comparison |
| Canonical text | 36 characters | 8-4-4-4-12 with hyphens |
| Hex without hyphens | 32 characters | Used in some APIs and logs |
| Base64url, no padding | 22 characters | Not a standard UUID form |
| Base32 | 26 characters | Rare, used by some ticketing systems |
Choosing, migrating, and testing
Pick v7 when the identifier is a database key, when you want to sort by creation time without a second column, or when you are building an append-heavy table. Pick v4 when the value is exposed publicly, when creation time is sensitive, or when the system already stores a timestamp column and gains nothing from ordering. Both are correct choices for different constraints, and mixing them in one table is usually a mistake.
Testing needs to assert structure rather than values. Check the version nibble, the variant nibble, and the byte length after parsing. For v7, assert that the embedded timestamp falls within the test window and that two values created in order compare correctly. Never snapshot a literal UUID in a test, and never assert that a v7 is strictly greater than the previous one unless the generator uses a counter; without one, same-millisecond order is random.
| Situation | Prefer | Reason |
|---|---|---|
| Primary key on a large table | v7 | Locality and smaller index |
| Public-facing identifier | v4 | No timestamp disclosure |
| Event log sorted by time | v7 | Native ordering without a column |
| Session or reset token | Neither | Use a purpose-built random token |
| Existing table with a time column | v4 | Ordering already available |
| Cross-system interchange | Either | Both are standard 128-bit UUIDs |
Questions
- Yes. Both are 128 bits, 16 bytes, and 36 characters in canonical text. Only the internal layout differs.
- Across milliseconds, yes, as raw bytes and as canonical text. Within a single millisecond the default order is random unless the generator uses a counter.
- v7, because inserts stay near the right edge of the B-tree instead of scattering across it. The trade-off is a write hotspot on the last page at very high concurrency.
- It reveals the creation time to the millisecond, and nearby values reveal record volume. Keep it internal or use v4 for identifiers that leave your system.
- No. A UUID is an identifier. Even a v4 value must be checked against access control on every request, and a token should come from a purpose-built generator with its own lifetime rules.
Are UUID v4 and v7 the same size?+
Does UUID v7 sort correctly?+
Which is better for a database primary key?+
Does UUID v7 leak information?+
Can I use a UUID as an access token?+
Examples are synthetic. Do not use a generated address for identity checks, account registration, or delivery.