UUID Generation: v4, v7, and ULID Comparison
Compare UUID v4, v7, ULID, and nanoid for generating unique identifiers with different tradeoffs in randomness, sortability, performance, and database index locality
Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.
UUID Generation: v4, v7, and ULID Comparison
Choose the right unique identifier strategy for your application by comparing UUID v4 (random), v7 (time-sortable), ULID (lexicographically sortable), and nanoid (compact URL-safe). This recipe covers generation, database index implications, collision probability, and migration considerations.
When to Use This
- Database primary keys need to be globally unique across distributed systems
- Identifier sortability affects query performance and index fragmentation
- URL-safe, short identifiers are needed for public-facing resources
Solution
1. UUID v4 (Random)
// ids/uuid4.ts
import { v4 as uuidv4 } from 'uuid';
const id = uuidv4(); // 'f47ac10b-58cc-4372-a567-0e02b2c3d479'
// Characteristics
// - Fully random (122 bits of randomness)
// - Not sortable by time
// - Causes index fragmentation in B-trees
// - Standard format with hyphens
2. UUID v7 (Time-Sortable)
// ids/uuid7.ts
import { v7 as uuidv7 } from 'uuid';
const id = uuidv7(); // '018f3bda-7c58-7e8a-8b5e-4f3e8c9d2a1b'
// Characteristics
// - First 48 bits = Unix timestamp in milliseconds
// - Remaining 74 bits = random
// - Sortable by creation time
// - Better index locality than v4
// - RFC draft standard (stable enough for production)
3. ULID (Lexicographically Sortable)
// ids/ulid.ts
import { ulid } from 'ulid';
const id = ulid(); // '01HV8J3K2M4N5P6Q7R8S9T0UV'
// Characteristics
// - 26 characters, Crockford's base32
// - First 10 chars = timestamp (sortable)
// - Last 16 chars = randomness
// - Lexicographically sortable as string
// - No hyphens, URL-safe
4. NanoID (Compact and Fast)
// ids/nanoid.ts
import { nanoid } from 'nanoid';
const id = nanoid(); // default 21 chars
const short = nanoid(10); // configurable length
// Characteristics
// - 21 chars by default (similar collision resistance to UUID v4)
// - Custom alphabet support
// - Fast generation (~50% faster than UUID)
// - URL-safe by default (no hyphens)
5. Comparison Matrix
// ids/comparison.ts
const comparison = {
uuidv4: {
length: 36,
sortable: false,
indexLocality: 'poor',
standard: 'RFC 4122',
collisionRisk: 'negligible (2^122)',
},
uuidv7: {
length: 36,
sortable: true,
indexLocality: 'good',
standard: 'RFC draft',
collisionRisk: 'negligible (2^74)',
},
ulid: {
length: 26,
sortable: true,
indexLocality: 'good',
standard: 'Community',
collisionRisk: 'negligible (2^80)',
},
nanoid: {
length: 21,
sortable: false,
indexLocality: 'poor',
standard: 'Community',
collisionRisk: 'negligible (2^126)',
},
};
6. PostgreSQL with UUID v7
-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Table with UUID v7 primary key
CREATE TABLE events (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY, -- use v7 in application
name TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- For sortable UUIDs, generate in application and insert
INSERT INTO events (id, name) VALUES ('018f3bda-7c58-7e8a-8b5e-4f3e8c9d2a1b', 'signup');
-- Index locality benefits: sequential inserts fill pages contiguously
How It Works
- UUID v4 uses randomness for uniqueness but scatters index inserts
- UUID v7 embeds a timestamp prefix, making inserts roughly sequential
- ULID uses base32 encoding for shorter, still sortable identifiers
- NanoID prioritizes speed and compactness with configurable length
Production Considerations
- Use UUID v7 for new applications needing time-sortable keys
- Keep UUID v4 for existing systems unless migration is justified
- Use ULID when identifier length and lexicographic sorting matter
- Use nanoid for short-lived tokens, short URLs, or when size is critical
Common Mistakes
- Generating UUIDs in the database instead of the application layer
- Using v4 in high-insert systems without monitoring index fragmentation
- Not handling the rare but possible UUID collision in distributed systems
FAQ
Q: Should I use auto-incrementing integers instead? A: Use integers for single-node systems where coordination is trivial. Use UUIDs for distributed systems or when identifiers must not reveal sequence information.
Q: Is UUID v7 officially standardized? A: It is in RFC draft status and widely considered stable. Major databases and libraries support it.