UUID v4 vs v7: Which Should You Choose? Randomness vs Sortability
When you need a unique identifier, UUIDs are the default. But v4 or v7? That question got a lot more interesting after RFC 9562 landed in 2024. v7 isn't just "a newer version." It addresses the core pain point v4 creates in databases, and getting this wrong can leave your primary key index fragmenting as data grows, with write throughput dropping by orders of magnitude.
This guide covers how each version is structured, how they perform, and where each fits.
UUID v4: The Random Default
v4 is by far the most widely used UUID version. Of its 128 bits, 122 are random; the other 6 carry version and variant markers.
// Standard v4 generation
const uuid = crypto.randomUUID();
// "f47ac10b-58cc-4372-a567-0e02b2c3d479"
What v4 gets right:
- Simple: standard library support everywhere,
crypto.randomUUID()ships in all modern browsers and Node.js - No coordination: two independent services generating v4 values will essentially never collide
- No timing leak: the UUID reveals nothing about when it was created
The downsides are mostly database-related, covered next.
UUID v7: The Time-Ordered Standard
v7 was standardized in 2024 by RFC 9562. The layout is fundamentally different:
- 48 bits: Unix millisecond timestamp (around 8900 years of range)
- 4 bits: version marker (7)
- 12 bits: random or monotonic counter (orders within the same millisecond)
- 2 bits: variant marker
- 62 bits: random
// v7 generation (requires a supporting library, e.g. uuid v11+)
import { v7 } from 'uuid';
const id = v7();
// "017f22e2-79b0-7cc3-98c4-dc0c0c07398f"
Notice the leading hex digits are the timestamp. v7 UUIDs sort naturally by creation time.
Why Sortability Matters In Databases
This is the reason v7 exists.
Most databases (PostgreSQL, MySQL, SQL Server) use B-trees for primary key indexes. B-trees perform best with ordered insertion. v4 values are random, so every insert lands somewhere arbitrary in the tree. Over time this causes:
- Page splits: when an insert target is full, the node splits, triggering extra disk IO
- Cache thrashing: random writes blow out the buffer pool as leaf pages get repeatedly evicted
- Fragmentation: the physical layout of the index gets progressively more scattered, hurting range scans
- Clustered index pain: in engines like InnoDB where the primary key dictates physical row order, v4 forces every insert to physically relocate data
v7 fixes this. UUIDs generated in the same time window are contiguous in the B-tree, so writes shift from random to roughly append-only.
The measurable impact is significant. In PostgreSQL tests inserting 10 million rows, v7 is typically 2 to 5x faster than v4, with index sizes 30% smaller or more. The exact numbers depend on hardware and configuration, but the direction is consistent.
Collision Probability: Is v4 Really Safe?
v4 has 122 bits of randomness, which makes collisions vanishingly unlikely but not impossible. The birthday paradox says you'd need around 2^61 (about 2.3 × 10^18) v4 UUIDs before reaching a 50% chance of one collision.
That's far beyond any real system's scale. But for high-throughput services generating millions of IDs per second over years of operation, the cumulative probability is worth thinking about. v7 narrows the collision window to the random portion within a single millisecond, and a monotonic counter drives the probability even lower.
v4 vs v7 Comparison
| Dimension | UUID v4 | UUID v7 |
|---|---|---|
| Random bits | 122 | 74 (plus timestamp) |
| Time-sortable | ✗ | ✓ |
| DB index friendly | ✗ (random writes) | ✓ (near-sequential) |
| Information leak | No timing exposure | Creation time inferable |
| Standardized | 2005 (RFC 4122) | 2024 (RFC 9562) |
| Library support | Universal | All major libraries |
| Sorted queries | Requires created_at column | ORDER BY id works |
When v4 Is The Right Choice
- Pure stateless systems: log entry IDs, cache keys, ephemeral tokens where ordering doesn't matter
- Mildly security-sensitive contexts: when you don't want IDs to expose creation time (though this "security" is weak, timestamps leak through many other channels)
- Legacy system compatibility: migration to v7 requires evaluating existing code assumptions
- Cross-system coordination: integrating with external systems still on v4
When v7 Wins
- Anything using a database primary key: the core design goal of v7
- Event logs and audit trails: naturally time-sorted, eliminating a separate created_at index
- High-throughput write systems: less IO amplification from B-tree fragmentation
- "Sort by time without a timestamp column" patterns: with caveats about timestamp leakage
Migration Notes
If you're moving from v4 to v7, keep these in mind:
- Storage format is unchanged: both are 128 bits, no schema change needed
- Leave existing rows alone: don't backfill; new rows use v7, old rows stay v4
- Sort semantics shift: queries that ordered by created_at can sometimes order by id instead, but only once all rows are v7
- Rebuild indexes: after migration, rebuild the primary key index once to reorganize the old v4 data
-- PostgreSQL: check primary key index bloat
SELECT pg_size_pretty(pg_relation_size('your_table_pkey'));
-- Rebuild if needed
REINDEX INDEX your_table_pkey;
Generate UUIDs Locally
Whether you need v4 or v7, you can generate them without depending on a server.
The UUID Generator supports bulk UUID generation entirely in the browser. No server round-trips, no logging. Especially useful when generating test fixtures or internal identifiers you don't want exposed to a third-party service.
Quick command-line options:
# Node.js (v4)
node -e "console.log(crypto.randomUUID())"
# Python (v4)
python3 -c "import uuid; print(uuid.uuid4())"
# v7 needs a third-party library
npx uuidv7
The short version: v4 is fine for stateless systems; for anything touching a database or needing chronological order, v7 is the sensible default in 2026. RFC 9562 has been out for two years, library support is solid, and there's no good reason for new projects to keep paying the index fragmentation tax that v4 imposes.
Implementer's note
There is exactly one right way to generate UUIDs in the browser: crypto.getRandomValues. Math.random output is predictable (its xorshift state can be reconstructed from a few samples), so using it for IDs stakes uniqueness on pseudo-randomness. Our generators all go through a CSPRNG — the client-side premise beneath every conclusion in this article.
Related Tools
Related Articles
China Resident ID Numbers: Structure, the MOD 11-2 Check Digit, and Why One in Eleven Ends in X
The 18-digit China resident ID (GB 11643) decoded for English-language systems: division, birthdate, sequence and gender fields, the ISO 7064 MOD 11-2 check digit with a hand-checkable example, and the corruption modes that survive naive validation — with a local validator.
China Unified Social Credit Code (USCC): Structure, Checksum Algorithm, and How to Validate It
The 18-char USCC (GB 32100-2015) decoded for English-language systems: field layout, the 31-character alphabet, the weighted mod-31 checksum with a worked example, and the failure modes that corrupt codes in real data — with a local validator.
How Color-Blind Users See Your Palette: A Six-Type Self-Check and Safe Colors
What does your UI look like to the 8% of men with color vision deficiency? The six types (protanopia/deuteranopia/tritanopia, both anomalies, achromatopsia), matrix-based simulation, a three-pair self-check list, and the WCAG practice of never encoding meaning in color alone.