Developers & Technical · Guide · Developer Utilities
How to choose between UUID versions
Every UUID version compared: v1 (MAC+time), v4 (random), v5 (deterministic), v7 (time-ordered). When to use each, database performance, alternatives.
UUIDs (Universally Unique Identifiers) are the most popular way to generate IDs without coordination. 128 bits, effectively guaranteed unique, no central authority needed. But “UUID” is not one thing — there are seven specified versions (v1 through v8) with very different properties. Picking the wrong one can tank database performance, leak timing information, or create predictable IDs in security-sensitive contexts. This guide covers what each version is for, which to use by default in 2026, the v4 vs. v7 debate for databases, and when UUIDs are the wrong choice entirely.
Advertisement
The UUID format
A UUID is 128 bits, typically rendered as 32 hex digits separated by hyphens: 550e8400-e29b-41d4-a716-446655440000.
The version is encoded in the first digit of the third group (here, “4”). The variant is in the first digit of the fourth group (here, “a” → RFC variant).
128 bits gives ~3.4×10^38 unique values. Collisions are effectively impossible for any practical purpose.
Version 4 — random (the default for years)
v4 UUIDs are 122 bits of random data + 6 fixed bits for version/variant. No information leakage, no timing, no MAC address. Just randomness.
Pros: universally supported, no metadata leaks, works everywhere, simple to generate.
Cons: not sortable. Inserted randomly into B-tree indexes, causes page splits and poor cache locality in databases. Random UUIDs as primary keys can cut database write throughput 3-10× compared to sequential IDs.
Use when: the ID is primarily a reference (not a hot index), or you need zero metadata leakage (security tokens, public-facing identifiers, event IDs).
Version 1 — MAC + timestamp
v1 encodes a 60-bit timestamp + 14-bit clock sequence + 48-bit node ID (often the MAC address of the generating machine).
Pros: partially sortable by time; guarantees uniqueness if the MAC is unique.
Cons: leaks machine MAC and generation time. Predictable. Old MAC-exposing implementations have been used to identify document authors after the fact.
Use when: almost never in 2026. v7 solved its legitimate use cases without the leakage. v1 remains in legacy systems.
Version 3 and 5 — namespace-based (deterministic)
v3 and v5 hash a name within a namespace (using MD5 or SHA-1). Same inputs always produce the same UUID.
Use when: deterministic ID generation — deduplicating records, generating consistent IDs from external keys (URLs, email addresses) across systems. Prefer v5 (SHA-1) over v3 (MD5) for collision resistance.
Warning: not random. An attacker who knows the namespace and can guess the name can compute the UUID.
Version 7 — time-ordered random (the new default for databases)
v7 was standardized in RFC 9562 (2024). First 48 bits are a Unix-millisecond timestamp; remaining 74 bits are random.
Pros: lexicographically sortable (newer UUIDs sort after older ones). Sequential enough to fit nicely into B-tree indexes, dramatically better database write performance than v4 while retaining most of v4’s randomness.
Cons: leaks generation time (millisecond precision). If that matters for your security model, use v4.
Use when: primary key in a database, high- cardinality index, event IDs where time-ordering helps. In most cases, v7 is the best default for new systems in 2026.
Version 6 — reordered v1
v6 is v1 with timestamp bits reordered so sorting works lexicographically. Rarely used; v7 superseded it in practice.
Version 8 — custom
v8 is the “do what you want” version for custom ID schemes that still want the UUID format. Useful when you need to embed specific data (tenant IDs, shard keys) while remaining UUID-compatible.
The v4 vs. v7 database decision
Old advice: “UUIDs destroy B-tree indexes; use auto-increment integers.” True for v4.
New advice: v7 bridges the gap. Benchmarks on PostgreSQL show v7 UUID inserts 2-5× faster than v4, and comparable to sequential bigint for index-locality.
Still choose bigint when: primary key is internal only, storage efficiency matters (8 bytes vs 16), you don’t need distributed generation.
Choose UUID (v7) when: IDs need to be generated without coordination (microservices, offline clients, pre-generation for bulk imports), IDs are exposed externally (URLs, APIs), merging data across systems.
Performance gotchas to understand
Storage: UUID is 16 bytes; bigint is 8. Across hundreds of foreign-key columns in a large schema, this matters (more RAM for indexes, more IO).
Text storage: storing UUIDs as strings (VARCHAR(36)) is 2-3× the storage and slower comparisons. Always use a native UUID type (Postgres, SQL Server), BINARY(16) (MySQL), or at minimum CHAR(36).
Index order: v4 inserts scatter randomly across the index, causing buffer pool churn. v7 and sequential bigints insert at the end.
When UUIDs are the wrong answer
Human-readable IDs: order numbers, invoice numbers, tickets. Customers can’t reliably read a UUID over the phone. Use short IDs (NanoID, ShortID) or custom sequences.
Short URLs: bit.ly-style redirects. UUIDs aren’t short. Use base62 encoding of sequential IDs, or NanoID (22 characters, URL-safe).
Security tokens: UUIDs are not cryptographically strong in all versions. Use a proper CSPRNG (crypto.randomBytes in Node, secrets in Python) for tokens. v4 is random enough in many cases but use explicit crypto libraries for auth tokens.
Alternatives to UUID
ULID (Universally Unique Lexicographically Sortable Identifier): 128 bits, timestamp + random, Crockford base32 encoded (26 chars). Case-insensitive, no special chars, URL-safe. Essentially v7 UUIDs with a nicer encoding. Slight decline in adoption since v7 became standard.
NanoID: shorter than UUID (22 chars by default), customizable alphabet. Good for URLs.
KSUID: segments-style timestamp + random, 27 chars. Sortable. Popular in some Go ecosystems.
Snowflake-style IDs: 64-bit time + machine + sequence. Twitter’s original design; now used by Discord, Instagram, etc. Tight, sortable, machine-coordinated. Fit bigint columns.
Generation best practices
Use a battle-tested library, not hand-rolled randomness. Node: crypto.randomUUID() (v4) or uuid package (v7+). Python: uuid module. Rust: uuid crate. Java: java.util.UUID.
Generate at the source. Let clients, mobile apps, or services generate IDs for their own entities before inserting. Avoids the “wait for database to return auto-increment ID” round-trip.
Store in native type. UUID column in Postgres/ SQL Server, BINARY(16) in MySQL. Avoid VARCHAR.
Don’t expose v1 UUIDs. If you generated v1 UUIDs historically, consider them low-entropy. Regenerate security-sensitive ones.
Quick decision tree
Need a primary key in a database? → v7.
Need a public-facing ID with zero metadata leakage? → v4.
Need deterministic ID from some source data (URL, email)? → v5.
Need a short user-facing ID? → NanoID, not a UUID.
Need a security token? → crypto.randomBytes, not a UUID.
Purely internal with zero distributed-generation need? → bigint auto-increment is fine.
Run the numbers
Generate UUIDs (v4 random or v1 timestamp) with the UUID generator. Pair with the password generator when you need cryptographically strong secrets instead of UUIDs, and the hash generator for v5-style deterministic ID computation from namespace and name.
Advertisement