UUID v1, v4 and v7: which identifier should you actually use?
Most teams reach for UUID v4 by reflex. That is usually fine, occasionally a performance mistake, and sometimes the wrong tool — here is how to tell which case you are in.
By Sk Md Rakib · Published · Updated · 10 min read
What a UUID guarantees
A UUID is 128 bits, conventionally written as 36 characters with four hyphens. Its promise is uniqueness without coordination: any machine can mint one at any time without asking a central authority, and the probability of a clash stays negligible. That property is what makes UUIDs the default identifier for distributed systems, offline-first clients and merge-heavy data pipelines.
Six bits are reserved for version and variant, so the practical randomness of a v4 UUID is 122 bits. At that size you would need to generate billions of identifiers per second for decades before a collision became likely — this is not the risk you should be worrying about.
The versions that matter
Nine versions exist on paper; three appear in real codebases.
- v1 — timestamp plus node identifier. Sortable-ish, but historically leaked the MAC address of the generating machine.
- v4 — random. Simple, private, unpredictable, and the correct default when you have no other requirement.
- v7 — a millisecond timestamp followed by randomness. Keeps v4's collision resistance while sorting in creation order, which makes it the modern choice for database primary keys.
The database problem nobody warns you about
A random v4 primary key lands in an arbitrary position in a B-tree index on every insert. On clustered-index engines such as InnoDB that means page splits, poor cache locality and fragmentation that grows with the table. The symptom is an insert path that quietly degrades as data volume rises — often blamed on the wrong thing entirely.
Time-ordered identifiers fix this because each new key appends near the right edge of the index. If you are choosing an identifier for a table that will grow large, prefer v7 (or an equivalent ordered id) as the storage key. If you already ship v4 keys, storing them as native 16-byte binary rather than a 36-character string still recovers a lot of space and index efficiency.
When a UUID is the wrong answer
UUIDs are unguessable, which is useful, but they are not access control — a resource reachable by anyone holding the id is still unauthorised access waiting to happen. They are also poor user-facing identifiers: nobody reads a UUID over the phone, and they consume a lot of space in URLs and logs.
For public-facing references consider a short, high-entropy slug (Nano ID style) for readability, or a signed opaque token when the identifier must not be enumerable. Reserve UUIDs for internal, machine-to-machine identity where their coordination-free property is what you are actually buying.
Generating them correctly
Whatever version you pick, the randomness must come from a cryptographically secure source. In the browser that is crypto.getRandomValues, which backs crypto.randomUUID and every reputable library. Math.random is not a substitute — it is fast, seeded and predictable.
Our UUID generator runs on the browser's crypto API and supports bulk output for seeding fixtures and test data. Because it is client-side, the identifiers it produces are never transmitted anywhere, which matters when you are minting keys that will end up in a real system.
Choosing identifiers for a real database
Picking a UUID version is only half the decision; how you store and index it determines whether the choice hurts. Random v4 values scatter across the index, so every insert lands in a different page and write amplification climbs as the table grows. Time-ordered v7 values append near the end of the index, which restores the locality you would get from an auto-incrementing integer while keeping the ability to generate identifiers on the client.
Storage type matters too. Keeping a UUID as a 36-character string costs roughly three times the bytes of a native 16-byte binary or uuid column, and that overhead is repeated in every secondary index and every foreign key. If your database has a dedicated type, use it; if it does not, store the raw bytes and format only at the edges where humans read them.
There are good reasons to prefer UUIDs over sequential integers regardless of cost. Clients can create identifiers offline and sync later without a coordination round trip. Merging data from multiple environments or shards no longer requires renumbering. And exposed identifiers stop leaking volume and ordering information, which sequential keys reveal to anyone who can read a URL.
What UUIDs do not give you is secrecy. They are unguessable enough to be inconvenient to enumerate, but a v1 value encodes a timestamp and a node identifier, and no version is a substitute for an authorisation check. Treat an identifier as a name, not a permission, and verify on every request that the caller is allowed to see the record it points to.
- Prefer v7 for new primary keys: unguessable, but index-friendly.
- Store as a native uuid or 16-byte binary column, not as text.
- Use v4 for tokens and correlation identifiers where ordering is irrelevant.
- Never rely on identifier obscurity in place of an access-control check.