UUID v4 vs UUID v7: Which UUID Version to Use

What a UUID Is

A UUID (Universally Unique Identifier) is a 128-bit value usually shown as 36 characters: 32 hex digits split by four dashes, like `550e8400-e29b-41d4-a716-446655440000`. The goal is simple. Generate identifiers that are unique across systems and time without a central coordinator.

The UUID standard, formally RFC 4122 and now updated by RFC 9562, defines several versions. In modern applications two matter most: v4 and v7. Older versions like v1 and v2 embed the host MAC address and a timestamp, which leaks machine identity and creates privacy issues. v3 and v5 are deterministic hash based values for when you need the same input to always produce the same UUID.

UUID v4: Random

UUID v4 is the version most developers know. It fills 122 of the 128 bits with cryptographically strong random data. The remaining 6 bits encode the version and variant, which lets parsers identify the format.

Strengths:

  • Simple. Generate random bytes and set the version bits.
  • No coordination. Any node can mint v4 UUIDs without clocks, sequence numbers, or network calls.
  • Privacy. The value carries no information about when or where it was made.

The catch is randomness itself. Random UUIDs scatter evenly across the entire 128-bit space. When you store them as the primary key in a B-tree indexed database, every insert lands at a random leaf. The index cannot append, it must split and rebalance constantly. On a busy table this causes write amplification and cache churn.

UUID v7: Time-Ordered

UUID v7 fixes the indexing problem. The first 48 bits encode a Unix timestamp in milliseconds. The remaining 74 bits hold randomness (or a monotonic counter) to keep values unique within the same millisecond.

Because the high bits are time, v7 UUIDs generated in sequence are also in sort order. New rows append to the right edge of the index instead of scattering randomly.

Strengths:

  • Index friendly. Inserts cluster at the end of the B-tree, so pages stay warm and splits are rare.
  • Sortable. You can order by the primary key and get creation order for free, useful for event logs and audit trails.
  • Time extractable. You can recover the creation timestamp from the UUID itself without an extra column.

The tradeoff is that v7 reveals roughly when an identifier was created, down to the millisecond. That is usually fine, but it can be a problem if you need to hide creation timing, for example in customer-facing URLs where you do not want to leak when a record was added.

Database Indexing: The Deciding Factor

This is where the choice becomes concrete. Most databases, including Postgres, MySQL, and SQLite, store primary keys in a B-tree index.

PropertyUUID v4UUID v7
Index insert patternRandomSequential at edge
B-tree page splitsFrequentRare
Write amplificationHigherLower
Cache localityPoorGood
Natural sort orderNoneBy creation time
Timestamp recoverableNoYes
Privacy of creation timeHiddenExposed

For a table with millions of rows and frequent inserts, the difference is measurable. Random inserts force the index to touch more pages per write, inflate the index size, and increase buffer pool turnover. Time-ordered inserts keep the active working set small and compact.

If you use UUIDs as primary keys in a relational database, v7 is the better default. If you use UUIDs only as opaque tokens, public IDs, or values that never get indexed, v4 stays fine.

Collision Probability

Both v4 and v7 effectively never collide in practice, but for different reasons.

For v4, the math is birthday-problem based. With 122 random bits, you would need to generate around 2 to the power of 61 UUIDs before reaching a 50 percent chance of a single collision. That number is astronomically larger than any realistic workload.

For v7, the timestamp removes most of the collision surface because values generated in different milliseconds can never match on the time bits. Within the same millisecond, the 74 bits of randomness (or a monotonic counter) keep values distinct. The practical collision risk is even lower than v4 under normal generation rates.

The real risk is not math, it is implementation. A weak random generator, a misconfigured sequence, or a clock that goes backwards can break uniqueness. Always use the platform's cryptographically secure random source.

When to Use Each

Use UUID v4 when:

  • You need pure opaque identifiers with no timing information.
  • The UUID is never a primary key or a frequently indexed column.
  • You are generating tokens, request IDs, or correlation IDs that live briefly.

Use UUID v7 when:

  • The UUID is a database primary key or part of a clustered index.
  • You want sortable IDs that reflect creation order.
  • You need to reconstruct a creation timestamp from the ID alone.
  • You are building event logs, audit trails, or append-only tables.

A common pattern is to use v7 as the internal primary key for indexing benefits, and expose a separate v4 as the public identifier in URLs and APIs. You get fast writes and no information leakage on the wire.

Migration Considerations

Switching UUID versions on an existing system takes care.

1. Check column types. Postgres has a native `uuid` type that accepts any RFC version. MySQL and others often store UUIDs as `CHAR(36)` or `BINARY(16)`, both of which hold v7 unchanged.

2. Do not convert existing values. Treat the switch as a change for new rows only. Mixing v4 and v7 in the same column is fine because both are valid 128-bit UUIDs.

3. Update generators first. Roll out v7 generation in code, deploy, and watch write latency on the busiest tables.

4. Watch for sort assumptions. If any logic assumed UUIDs were unsortable, v7 will now produce ascending order. Usually this is a welcome side effect, but verify.

5. Regenerate test fixtures. Tests that hardcode UUIDs may need updating if they assert on format alone. Both versions share the same display shape, so most assertions pass.

Generation Examples

Most languages now ship v7 support, or have libraries that do.

JavaScript (Node):


// Node 22+ has crypto.randomUUID v7 support planned
// Today, use a library like 'uuid' v11+
import { v4, v7 } from 'uuid';
const id4 = v4(); // '4ec1d3a0-...';
const id7 = v7(); // '0190b3d4-...' time-prefixed

Postgres:


-- v4 built in
SELECT gen_random_uuid();

-- v7 via extension or expression
-- several community extensions add gen_random_uuid7()

Python:


import uuid
id4 = uuid.uuid4()
# v7 is not in the stdlib yet; use the 'uuid-utils' or 'uuid7' package

Both formats share the same 36 character shape and the same dash placement, so storage, parsing, and validation code does not change.

Pair UUIDs with Strong Secrets

A UUID identifies a record. It is not a credential and it is not unguessable enough to act as a password. When you need a secret, such as an API token or a session key, generate it with a dedicated random source at higher entropy. Use the [password generator](/en/password-generator) to produce cryptographically strong values sized to your threat model, then store the hash and never the plaintext. Keep UUIDs for identity and use real secrets for access.