The UUID Versions Explained: What v1 to v8 (and Nil) Are For
· by Andergrove Software
Almost everyone generates a UUID with "v4" and never thinks about it again. But RFC 9562 (the 2024 standard that replaced RFC 4122) defines a whole family of versions, and each one solves a different problem: some encode the time they were created, some are derived from a name so they come out identical every time, and one is pure randomness. Pick the wrong version and you either leak information you did not mean to, or miss a property you needed.
This is a field guide to the UUID versions: what each one is, how it is built, and when to reach for it. If you only care about the database primary-key decision, the companion post on UUID v4 vs. v7 goes deeper on that; this is the wider map. You can generate v4 UUIDs in the UUID generator as you read.
How a UUID is laid out
A UUID is a 128-bit value, almost always written as 32 hexadecimal digits in five dash-separated
groups: xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx. Two positions carry meaning:
- The
Mdigit is the version (1 to 8). - The top bits of the
Ndigit are the variant, which says which family of layout rules applies. For every version here it is the same "variant 1."
Everything else is filled according to the version's rules. So you can read a UUID's version
straight off the 13th hex digit: a 4 there means v4, a 7 means v7, and
so on.
Time-based: v1 and v6
v1 encodes a 60-bit timestamp (100-nanosecond intervals since 1582), a clock sequence, and a node identifier that was traditionally the machine's MAC address. That makes v1 roughly sortable and unique per machine, but it leaks two things: when the UUID was made and which machine made it. That MAC-address exposure is why many people avoided v1.
v6 uses the same ingredients as v1 with the timestamp bits reordered so the value sorts in time order as a plain string or binary blob. It is the "fixed" v1: use it when you want v1-style time encoding but also want the IDs to sort, and prefer a random node over a real MAC. If your real goal is time-ordered IDs for a database, v7 (below) is usually the better modern choice.
Name-based: v3 and v5
These two are the odd ones out: they are deterministic. Give them the same input and they always produce the same UUID. You hash a namespace UUID together with a name (any string), and the digest becomes the UUID.
- v3 uses MD5.
- v5 uses SHA-1.
The point is reproducibility: anyone who knows the namespace and name can recompute the exact same ID without coordinating, which is handy for deriving a stable ID from something like a URL or a username. Prefer v5 over v3, since MD5 is the weaker hash. Note that neither is for secrets, because the output is reproducible by design; the hash here only spreads the input deterministically, it does not protect anything (see SHA-256 explained for when a hash being "broken" actually matters).
Random: v4
v4 fills almost all of its 122 free bits with random data. No timestamp, no node, nothing to leak, no coordination needed, and collisions are astronomically unlikely. That simplicity is why it became the default for application-generated IDs and the only version most libraries expose out of the box. The trade-off, for database primary keys, is that randomness hurts index locality, which is the full story in UUID v4 vs. v7.
Time-ordered: v7
v7 is the modern recommendation for new systems that want UUIDs as keys. It puts a 48-bit Unix millisecond timestamp at the front, then fills the rest with randomness. Because the leading bits grow over time, v7 values sort in roughly creation order, so they stay index-friendly like an auto-increment key while keeping the "generate anywhere, no collisions" property of a UUID. The cost is that a v7 reveals roughly when it was created. (If the millisecond timestamp is unfamiliar, see Unix timestamps explained.)
Custom and the sentinels: v8, nil, max
- v8 is the "do it yourself" version: the layout is vendor-defined, for when you need to pack your own data into a UUID-shaped value while staying a valid UUID. Reach for it only when you have a specific reason.
- The nil UUID is all zeros,
00000000-0000-0000-0000-000000000000, a conventional "none" or "empty" sentinel. - The max UUID is all ones,
ffffffff-ffff-ffff-ffff-ffffffffffff, the opposite sentinel and a handy upper bound.
Which one should I pick?
- Default, and any ID you expose publicly: v4. Random, leaks nothing.
- Database primary keys in a new system: v7. Time-ordered and index-friendly (see v4 vs. v7).
- A stable ID derived from a name or URL: v5 (not v3).
- Legacy time-plus-node, but sortable: v6, with a random node rather than a MAC.
- Packing custom data UUID-style: v8.
- "Empty" or sentinel values: nil (or max for an upper bound).
Generate one
The Andergrove UUID Generator produces v4 UUIDs in bulk,
entirely in your browser, with uppercase and no-dash options. For the other versions, most
ecosystems ship a small library or a built-in: Postgres 18 has uuidv7(), and Node,
Python, Go and Rust all have well-maintained UUID packages covering v1 and v3 through v8. For the
database-key trade-off between random and time-ordered IDs, read
UUID v4 vs. v7.