UUID v4 vs UUID v7 vs ULID vs NanoID: Comparison Guide
The Evolution of Primary Keys: UUID v4 vs v7 vs ULID vs NanoID
For decades, auto-incrementing integer IDs (1, 2, 3, 4...) were the universal standard for relational database primary keys. While simple and efficient for single-node architectures, sequential integers suffer from critical architectural and security limitations in modern distributed environments:
- Security & Business Intelligence Scraping: Predictable sequential IDs expose transaction volume, total user counts, and internal database records to competitors via URL parameter enumeration attacks.
- Distributed Multi-Master Write Conflicts: Coordinating auto-incrementing counters across multi-region databases or decoupled microservices requires centralized coordination locks, destroying horizontal scalability.
- Client-Side Generation Impossibility: Web clients and mobile apps cannot generate sequential IDs offline before syncing with central servers.
To resolve these challenges, modern distributed software architectures rely on globally unique identifiers. However, choosing between UUID v4, UUID v7, ULID, and NanoID requires understanding key trade-offs between cryptographic entropy, database B-Tree index cache efficiency, lexicographical sortability, and URL string compactness.
You can generate, inspect, and benchmark all four identifier formats live in your browser using our client-side **UUID & ULID Generator, or explore clustered B-Tree write metrics in our UUID v4 vs UUID v7 Comparison Guide**.
Complete Comparison Matrix: UUID v4 vs v7 vs ULID vs NanoID
| Identifier | Bit Width & Encoded Length | Total Entropy / Collision Resistance | Time-Sortable? | Standard / Specification | Canonical Alphabet | Ideal Use Cases |
|---|---|---|---|---|---|---|
| UUID v4 | 128 bits (36 chars with hyphens) | 122 bits pseudo-random entropy | โ No (Purely Random) | RFC 4122 / RFC 9562 | Hexadecimal (0-9, a-f) | Ephemeral session tokens, distributed event IDs, non-indexed payloads |
| UUID v7 | 128 bits (36 chars with hyphens) | 48-bit Unix ms timestamp + 74 bits random | โ Yes (Unix millisecond order) | RFC 9562 (IETF Standard) | Hexadecimal (0-9, a-f) | Primary keys in PostgreSQL, MySQL, SQLite, and distributed SQL databases |
| ULID | 128 bits (26 characters) | 48-bit Unix ms timestamp + 80 bits random | โ Yes (Lexicographically sortable) | Defacto Open Spec (alizain/ulid) | Crockford's Base32 (Case-insensitive) | URL route slugs, DynamoDB partition keys, Kafka message keys |
| NanoID | Configurable (Default 126 bits / 21 chars) | 126 bits cryptographic entropy | โ No (Purely Random) | Open Source (ai/nanoid) | URL-Safe Base64 (A-Za-z0-9_-) | Short URLs, public invitation codes, DOM elements, frontend state tokens |
UUID v4: Pure Randomness & RFC 4122 Standard
UUID v4 is the most ubiquitous 128-bit identifier in computing. Standardized in RFC 4122 (and reaffirmed in RFC 9562), UUID v4 generates 122 cryptographically secure random bits with 6 fixed version and variant bits:
f47ac10b-58cc-4372-a567-0e02b2c3d479
^ ^
โ โโโ Variant 1 (RFC 4122): bits '10xx' (8, 9, a, or b)
โโโโโโโโ Version 4: 4 bits '0100'When to Use UUID v4:
- Stateless Tokens & Nonces: Perfect for OAuth state tokens, CSRF tokens, and one-time password reset nonces.
- Zero Timestamp Leakage: Does not embed generation timestamps, ensuring complete temporal privacy.
- Universal Native Support: Standard library support in almost every programming language (e.g. crypto.randomUUID() in JavaScript, uuid.uuid4() in Python).
UUID v7: The Modern Relational Database Standard (RFC 9562)
Approved by the IETF in RFC 9562, UUID v7 represents the modern evolution of unique identifiers for database systems. UUID v7 combines the standardized 36-character hyphenated format and 128-bit size of traditional UUIDs with a 48-bit millisecond Unix timestamp:
018e3a2b-7c40-7f12-8921-998877665544
โโโ 48-bit Timestamp โโโ โโ Ver/Var + 74 Random bits โโKey Architectural Advantages of UUID v7:
- RFC Standard Compliance: Officially recognized across database drivers, ORMs, and serialization protocols.
- Sub-Millisecond Monotonicity: Optional counter bits guarantee strict ordering for multiple IDs generated in the exact same millisecond.
- Native 128-bit DB Storage: Fits cleanly into native PostgreSQL UUID and MySQL BINARY(16) database column types without custom schema migrations.
B-Tree Index Fragmentation: UUID v4 vs UUID v7 Performance Benchmark
The most significant distinction between UUID v4 and UUID v7 lies in how relational database storage engines (such as PostgreSQL's nbtree and MySQL InnoDB's clustered B+ Trees) organize data on physical NVMe/SSD storage.
Why Random UUID v4 Destroys Index Performance: - Random Leaf Node Insertion: Because UUID v4 values have uniform random distribution, new rows are inserted at random page locations throughout the entire B-Tree index. - Frequent Page Splits: When an index leaf page fills up (typically 8KB or 16KB), the database must split the page in half, rewrite sibling pointers, and flush two separate physical disk blocks. - Buffer Pool Thrashing: As table size exceeds available RAM, every insert requires reading a random page from disk into cache, evicting active pages and causing high I/O wait times.
How UUID v7 Restores Fast Append-Only Insertion: Because the first 48 bits of UUID v7 represent a monotonically increasing Unix timestamp, every newly inserted record lands naturally on the rightmost leaf page of the B-Tree index. This eliminates mid-tree page splits, keeps active index pages hot in RAM, and achieves insertion throughput comparable to sequential integer primary keys (typically 40% to 60% faster insert speeds with up to 30% reduction in index bloat).
ULID: Lexicographically Sortable Crockford Base32 Identifier
ULID (Universally Unique Lexicographically Sortable Identifier) is a 128-bit identifier formatted as 26 uppercase characters using Crockford's Base32 alphabet (0123456789ABCDEFGHJKMNPQRSTVWXYZ):
- 48-Bit Timestamp: First 10 characters encode millisecond Unix time (sufficient until the year 10889 AD).
- 80-Bit Randomness: Final 16 characters provide cryptographic randomness with monotonic incrementing fallback.
- Crockford's Base32 Readability: Excludes confusing characters (
I,L,O,U) to eliminate ambiguity in human reading and voice transcription. - Case-Insensitive & URL-Safe: Contains no hyphens, slashes, or URL special characters, making it clean in REST API route parameters (
/api/orders/01ARZ3NDEKTSV4RRFFQ69G5FAV).
NanoID: Compact, URL-Safe & Collision-Resistant Identifier
NanoID is a lightweight, customizable string identifier designed for web applications where character efficiency is paramount:
- 21 Characters Default: Delivers 126 bits of entropy (comparable to UUID v4) in a 40% shorter string length.
- URL-Safe Alphabet: Uses
A-Za-z0-9_-without requiring URL encoding or character escaping. - Configurable Alphabet & Length: Developers can define custom character sets (e.g. numeric-only
0-9, lowercase alphanumeric) and adjust lengths dynamically. - Tiny Bundle Size: Under 130 bytes in JavaScript bundles, making it ideal for high-performance frontend applications.
How to Choose Between UUID v4, v7, ULID, and NanoID (Step-by-Step)
Step 1: Evaluate Database Storage and B-Tree Indexing Requirements If your identifier serves as a primary key or foreign key in a relational database (PostgreSQL, MySQL, SQLite, Oracle) where writes exceed tens of thousands of records, select UUID v7. Its monotonically increasing 48-bit timestamp prefix ensures append-only index leaf writes, preventing severe B-Tree page splits and disk I/O thrashing. If you do not require database indexing, proceed to Step 2.
Step 2: Assess Privacy and Creation Timestamp Leakage Risks Determine whether your application can safely disclose the exact millisecond an identifier was created. Both UUID v7 and ULID embed timestamp metadata in their first bits. If competitors could exploit creation times to deduce user registration rates, daily order volumes, or system activity, avoid time-sorted IDs and choose UUID v4 or cryptographically hashed tokens.
Step 3: Determine URL Aesthetics and String Length Budget Analyze where the identifier appears in your architecture. If the ID is exposed to humans in SMS messages, short URL slugs, QR codes, or mobile screens, a 36-character hyphenated UUID creates visual clutter. For compact user-facing links, choose NanoID (21 characters) or ULID (26 characters). ULID offers the added advantage of Crockford Base32 case-insensitivity without ambiguous characters like O and 0.
Step 4: Verify Multi-Language and ORM Standardization Support Review your technology stack across backend services, third-party libraries, and ORMs (Prisma, TypeORM, Drizzle, Hibernate, SQLAlchemy). UUID v4 has 100% universal standard library support across every language. UUID v7 is standardized under IETF RFC 9562 and natively supported in modern database drivers. ULID and NanoID require third-party libraries in languages without native implementations.
Step 5: Test and Benchmark Locally Using Client-Side Tools Before committing database schema migrations, benchmark collision rates and inspect identifier structures using our free client-side developer utilities. Use the **UUID & ULID Generator** to generate sample batches of UUID v4, UUID v7, ULID, and NanoID strings, validate timestamp decoding, and export SQL seeds for performance testing in your staging environment.
Multi-Language Code Implementations (JS/TS, Python & Go)
1. JavaScript & TypeScript (Web Crypto API & RFC 9562)
// 1. Native UUID v4 (Zero dependencies, Node 14.17+ & modern browsers)
export function generateUUIDv4(): string {
return crypto.randomUUID();
}
// 2. Pure TypeScript UUID v7 Generator (RFC 9562 compliant)
export function generateUUIDv7(): string {
const timestamp = Date.now();
const bytes = new Uint8Array(16);
crypto.getRandomValues(bytes);
// 48-bit timestamp in big-endian
bytes[0] = (timestamp / 0x10000000000) & 0xff;
bytes[1] = (timestamp / 0x100000000) & 0xff;
bytes[2] = (timestamp / 0x1000000) & 0xff;
bytes[3] = (timestamp / 0x10000) & 0xff;
bytes[4] = (timestamp / 0x100) & 0xff;
bytes[5] = timestamp & 0xff;
// Version 7 (0b0111)
bytes[6] = (bytes[6] & 0x0f) | 0x70;
// Variant 1 RFC 4122 (0b10xx)
bytes[8] = (bytes[8] & 0x3f) | 0x80;
const hex = Array.from(bytes, (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)}`;
}
// 3. NanoID Implementation with Web Crypto
export function generateNanoId(size = 21): string {
const alphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLFGQZ_flavor";
const bytes = crypto.getRandomValues(new Uint8Array(size));
let id = "";
for (let i = 0; i < size; i++) {
id += alphabet[bytes[i] & 63];
}
return id;
}2. Python 3 (Standard Library & Modern UUID Generators)
import uuid
import time
import os
# 1. UUID v4 (Standard Library)
v4_id = uuid.uuid4()
print("UUID v4:", v4_id)
# 2. UUID v7 (RFC 9562 via Python standard libraries)
def generate_uuid7() -> uuid.UUID:
ns = time.time_ns()
timestamp_ms = ns // 1_000_000
rand_bytes = bytearray(os.urandom(10))
# 48-bit millisecond timestamp
time_bytes = timestamp_ms.to_bytes(6, byteorder='big')
raw = bytearray(time_bytes + rand_bytes)
# Set version 7 (0b0111) and variant 1 (0b10xx)
raw[6] = (raw[6] & 0x0F) | 0x70
raw[8] = (raw[8] & 0x3F) | 0x80
return uuid.UUID(bytes=bytes(raw))
print("UUID v7:", generate_uuid7())3. Go (google/uuid & RFC 9562 Native Support)
package main
import (
"fmt"
"github.com/google/uuid"
)
func main() {
// 1. UUID v4 (Cryptographic Random)
v4, err := uuid.NewRandom()
if err == nil {
fmt.Println("UUID v4:", v4.String())
}
// 2. UUID v7 (Time-Ordered RFC 9562)
v7, err := uuid.NewV7()
if err == nil {
fmt.Println("UUID v7:", v7.String())
fmt.Println("V7 Timestamp Unix:", v7.Time().UnixTime())
}
}4. SQL & Relational Databases (PostgreSQL, MySQL & SQLite)
-- PostgreSQL with native UUID type
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Legacy UUID v4
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- PostgreSQL with UUID v7 extension or native function (Postgres 17+)
CREATE TABLE orders_v7 (
id UUID PRIMARY KEY, -- Populated with UUID v7 for sequential B-tree writes
customer_id UUID NOT NULL,
total_amount NUMERIC(10, 2) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- MySQL 8+ optimized 16-byte binary primary key
CREATE TABLE users (
id BINARY(16) PRIMARY KEY, -- Stores raw 128-bit UUID v7
email VARCHAR(255) NOT NULL UNIQUE
);Security, Privacy & Timestamp Leakage Considerations
When adopting time-ordered identifiers like UUID v7 and ULID, security engineers must evaluate timestamp disclosure risks:
- Timestamp Leakage: Because UUID v7 and ULID embed millisecond creation timestamps in the upper 48 bits, anyone who observes the identifier can deduce exactly when the account, document, or transaction was created.
- When Timestamp Leakage is Unacceptable: For password reset tokens, anonymous voting records, and confidential user IDs, use UUID v4 or cryptographically hashed nonces.
- Client-Side Generation Privacy: Generating UUIDs and ULIDs in-browser ensures zero external network leakage. Telekit Tools generates all identifiers 100% client-side via the browser's Web Crypto API with zero server logging.
Related Developer & Security Tools
Enhance your identifier and cryptographic workflow with these client-side utilities:
- **UUID & ULID Generator**: Generate bulk UUID v4, v7, and ULID identifiers with custom case and formatting
- **UUID v4 vs UUID v7 Comparison Guide**: Side-by-side performance benchmarks, database index metrics, and migration guide
- **Random Number Generator**: Generate cryptographically secure random integers and sequences
- **Hash Generator**: Compute SHA-256, SHA-512, and MD5 cryptographic hashes
- **Password Hash Generator**: Hash secrets with bcrypt and Argon2
- **Base64 Encoder/Decoder**: Encode binary identifiers and tokens into Base64URL
- **Base64 Encoding Guide**: Learn Base64URL string encodings and binary conversion techniques