The Complete Guide to UUIDs: v4 vs v7 and When to Use Each
What is a UUID?
A UUID (Universally Unique Identifier) is a 128-bit label used to uniquely identify information in distributed systems. UUIDs are designed so that identifiers generated by different parties—at different times, on different machines—will almost never collide. They’re the backbone of record identification in microservices, event-sourced systems, and any database that can’t rely on a central auto-increment counter.
A UUID is typically rendered as 32 hexadecimal digits grouped into five segments, separated by hyphens:
550e8400-e29b-41d4-a716-446655440000
The format is 8-4-4-4-12, and the 13th character (the first digit of the third group) indicates the UUID version. In the example above, the 4 at position 14 tells us this is a version 4 UUID.
You can generate UUIDs instantly with the UUID Generator tool on CodeKit—no server round-trip required.
UUID v4: The Random Workhorse
UUID v4 is the most widely used version. It relies on cryptographically secure random numbers to fill 122 of the 128 bits (the remaining 6 bits encode the version and variant). With 2^122 possible values, the chance of a collision is astronomically small—roughly one in a billion after generating a billion UUIDs.
// Generating a v4 UUID in the browser
crypto.randomUUID(); // "550e8400-e29b-41d4-a716-446655440000"
// Or manually with getRandomValues
function uuidv4() {
const bytes = crypto.getRandomValues(new Uint8Array(16));
bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4
bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant 10
const hex = [...bytes].map(b => b.toString(16).padStart(2, '0'));
return `${hex.slice(0,4).join('')}-${hex.slice(4,6).join('')}-${hex.slice(6,8).join('')}-${hex.slice(8,10).join('')}-${hex.slice(10,16).join('')}`;
}
Why v4 is so popular
- No coordination needed: Any client can generate a valid UUID without contacting a server.
- No leakage: Random UUIDs reveal nothing about creation time, host, or sequence.
- Universal support: Available in every major language and database.
The downside? Randomness comes at a cost when you store millions of rows in a B-tree index.
UUID v7: Time-Ordered and Index-Friendly
UUID v7 was finalized in RFC 9562 (2024). It combines a 48-bit Unix timestamp (in milliseconds) with 74 bits of random data. The result is a UUID that is both unique and sortable by creation time.
018f6a1c-5e3b-7abc-9def-0123456789ab
^^^^^^^^ ^^^^ ^ <-- timestamp prefix
Because the most significant bits encode time, v7 UUIDs generated in sequence are naturally ordered. This has profound implications for database performance.
// Generating a v7 UUID (Node.js 22+ has built-in support)
import { randomUUID } from 'node:crypto';
randomUUID(); // may return v4 by default
// Manual v7 implementation
function uuidv7() {
const timestamp = Date.now();
const bytes = crypto.getRandomValues(new Uint8Array(10));
const view = new DataView(new ArrayBuffer(16));
// 48-bit timestamp in the first 6 bytes
view.setUint32(0, Math.floor(timestamp / 0x100000000));
view.setUint16(4, timestamp & 0xffff);
// version 7
view.setUint8(6, (bytes[0] & 0x0f) | 0x70);
view.setUint8(7, bytes[1]);
// variant 10
view.setUint8(8, (bytes[2] & 0x3f) | 0x80);
for (let i = 9; i < 16; i++) view.setUint8(i, bytes[i - 6]);
const hex = [...new Uint8Array(view.buffer)]
.map(b => b.toString(16).padStart(2, '0')).join('');
return `${hex.slice(0,8)}-${hex.slice(8,12)}-${hex.slice(12,16)}-${hex.slice(16,20)}-${hex.slice(20)}`;
}
The Database Index Problem
This is where v4 and v7 diverge sharply. Most databases use B-tree indexes, which work best when inserts are ordered. Here’s what happens with each version:
UUID v4 inserts are random. Each new row lands at a random position in the index. This causes:
- Page splits: The B-tree must constantly rebalance, fragmenting storage.
- Cache thrashing: Random access defeats the buffer pool’s locality of reference.
- Write amplification: More I/O per insert as the tree reorganizes itself.
UUID v7 inserts are sequential. New rows append to the end of the index, which is already in memory. The result is tighter, faster, and more cache-friendly.
| Metric (1M rows, PostgreSQL) | UUID v4 | UUID v7 |
|---|---|---|
| Index size | Larger | Smaller |
| Insert throughput | Lower | Higher |
| Range scans by creation time | Slow | Fast |
| Fragmentation | High | Low |
If you ever need to query “the 100 most recent records,” v7 lets the database walk the index in order. With v4, you need a separate created_at column and a compound index.
When to Use Each
Choose UUID v4 when
- You need absolute unpredictability—for example, session tokens or shareable links where guessing the next ID is a security risk.
- Your dataset is small (under a few hundred thousand rows), so index fragmentation doesn’t matter.
- You’re working in a legacy system that already uses v4 everywhere.
- You can’t guarantee clock accuracy across distributed nodes.
Choose UUID v7 when
- You’re designing a new system and want both uniqueness and sortability.
- High write throughput matters—event logs, telemetry, audit trails.
- You want to drop a
created_atcolumn because the UUID itself is time-ordered. - You’re using a distributed database (Cassandra, DynamoDB, Spanner) that benefits from monotonically increasing keys.
Other Versions Worth Knowing
- v1: Time-based with a MAC address. Unique but leaks the host’s network card—rarely used today.
- v3 / v5: Name-based, deterministic. Same input always produces the same UUID. Great for stable, derived identifiers.
- v6: Like v1 but with the timestamp reordered to be sortable. Largely superseded by v7.
- v8: Custom format—define your own bits. Useful for specialized sharding schemes.
Best Practices
-
Store as a native type. PostgreSQL has
uuid; MySQL 8.0+ hasBINARY(16). Don’t store UUIDs asVARCHAR(36)—it wastes space and breaks indexing. -
Index with care. If you must use v4, consider a separate sequential column (like an auto-increment or a v7-based “shard key”) for clustering.
-
Don’t parse the timestamp from v7 in application logic. Treat the UUID as an opaque identifier. If you need
created_at, store it explicitly—it’s clearer and survives version migrations. -
Use a library. Hand-rolled UUID code is a common source of bugs. The
uuidnpm package and Python’suuidmodule support v7 in recent versions.
Conclusion
UUID v4 remains the safe, default choice for general-purpose uniqueness. But if you’re building anything that writes a lot of rows or needs time-ordered queries, UUID v7 is a meaningful upgrade—it gives you the uniqueness of a UUID with the index behavior of an auto-increment.
The next time you reach for a random UUID, ask yourself: do I need randomness, or do I just need uniqueness? If it’s the latter, v7 will make your database happier.
Want to try both versions side by side? Open the UUID Generator on CodeKit and generate v4 and v7 UUIDs directly in your browser.