UUIDs in practice: v4, v7, and choosing an ID scheme that scales
Random IDs you can generate anywhere, with no coordination — but the version you pick has real consequences for your database. Here's how to choose.
At some point every system needs to name things: this user, that order, this
uploaded file. The classic answer is an auto-incrementing integer from the
database — 1, 2, 3. It's compact and fast, but it has a catch: only the
database can hand out the next number, which means you can't know an entity's ID
until you've written it, and you can't merge two datasets without collisions.
UUIDs — Universally Unique Identifiers — solve that by being large and random enough that any machine can generate one, any time, with a vanishing probability of ever colliding with another. That "generate anywhere, no coordination" property is why they're everywhere in distributed systems. But not all UUIDs are the same, and the version you choose has real performance consequences. Let's sort it out.
What a UUID looks like
A UUID is a 128-bit value, conventionally written as 32 hexadecimal digits in five hyphen-separated groups:
550e8400-e29b-41d4-a716-446655440000
That's 8-4-4-4-12 characters. Two positions carry meaning: one digit encodes the version (which generation algorithm produced it) and a couple of bits encode the variant (which layout standard it follows). Everything else is payload — random bits, timestamp bits, or both, depending on the version.
128 bits is an almost incomprehensibly large space: about 3.4 × 10³⁸ values. This is what makes "just generate a random one and assume it's unique" a safe bet in practice rather than a gamble.
The versions that matter
Several UUID versions exist, but in modern application code you'll almost always be choosing among three.
Version 4: pure random
UUIDv4 is 122 random bits (the rest is version/variant markers). It's the
default almost everywhere, and for good reason: it needs no input, no state, and
no coordination — just a good random number generator. If you call a uuid()
function without thinking, you're probably getting a v4.
The collision math is reassuring. You'd need to generate on the order of a billion UUIDs per second for decades before a duplicate became likely. For practically every application, v4 uniqueness is a solved problem.
Its one weakness is not uniqueness — it's ordering, which we'll get to.
Version 7: time-ordered
UUIDv7 is the newer option (standardized in 2024's RFC 9562) and it's increasingly the recommended default for database primary keys. It puts a millisecond Unix timestamp in the high bits and fills the rest with randomness. The result is still globally unique and still generatable anywhere — but because the timestamp leads, v7 UUIDs sort in roughly the order they were created.
That single property fixes v4's big database problem (below) while keeping everything good about random UUIDs. If you're picking an ID scheme for a new table today, v7 is usually the right starting point.
Version 1: timestamp + MAC address
UUIDv1 also embeds a timestamp, but it traditionally mixes in the machine's MAC address, which can leak information about the host that generated it — a mild privacy and security concern. v7 is essentially the modern replacement: time-ordered without the hardware fingerprint. New systems should prefer v7 over v1.
The one that bites: random UUIDs and database indexes
Here's the practical reason the v4-vs-v7 choice matters, and it surprises people who assumed "an ID is an ID."
Most databases store their primary key in a B-tree index, and B-trees are happiest when new keys arrive in roughly increasing order — each insert lands near the "end," reusing recently-touched pages. Auto-increment integers do this naturally.
UUIDv4 is the opposite: every new key is random, so inserts scatter all over the index. On a large table this causes page splits and poor cache locality — the database constantly loads and rearranges index pages that were nowhere near each other. The symptom is insert performance that quietly degrades as the table grows, plus a larger, more fragmented index.
UUIDv7 sidesteps this entirely. Because the timestamp leads, new keys are monotonically increasing (approximately), so inserts append near the end just like an integer would. You get the "generate anywhere" freedom of a UUID with the insert locality of a sequential ID. This is the whole reason v7 exists, and why "use v7 for primary keys, v4 when you just need a random token" has become common advice.
When to use which
A practical decision guide:
- Database primary keys → UUIDv7. Global uniqueness plus index-friendly ordering.
- Public-facing IDs in URLs (
/orders/{id}) → v4 or v7. Both avoid exposing a sequential count of your records — with integer IDs,/orders/1042tells a competitor roughly how many orders you've had, and lets anyone enumerate/orders/1,/orders/2. A UUID reveals nothing and can't be guessed. - Random tokens, nonces, correlation IDs → v4. You want maximum randomness and don't care about ordering.
- Interop with a system that mandates a version → whatever it requires.
A caveat on "unguessable"
A common temptation is to use a random UUID as a security token — a password
reset link, an unsubscribe URL, an unauthenticated "anyone with the link" share.
v4 is random enough that this is often fine, but with one caveat: a UUID is
unique, not secret by design. It can end up in server logs, browser history, the
Referer header, and analytics. For genuinely sensitive capabilities, treat a
UUID like any other bearer secret — keep it out of logs, give it an expiry — or use
a purpose-built cryptographic token. For "hard to guess so people can't enumerate
each other's records," a v4 UUID does the job well.
Generating them
You rarely need to hand-write a UUID, but you often need one right now — a test
fixture, a seed row, a config value, a placeholder ID while prototyping. A
UUID Generator that runs in your browser gives you a fresh,
correctly-formatted identifier on demand, which beats copying the same
00000000-... placeholder everywhere and later discovering two records share it.
The takeaway
UUIDs let any machine mint a globally-unique 128-bit ID with no coordination, which is why distributed systems love them. Reach for v7 as your default for database keys — it's unique and time-ordered, so it plays nicely with B-tree indexes instead of fragmenting them. Reach for v4 when you just need a random, order-independent token. And remember that "unique" isn't the same as "secret": a UUID is safe against guessing and enumeration, but for truly sensitive links, treat it with the same care as any other credential.