UUID v4 and ULID both generate globally unique identifiers without a central coordinating authority, but they solve different problems. UUID v4 optimizes for maximum randomness and unpredictability; ULID optimizes for database-friendly sortability while staying collision-resistant. The choice has real performance consequences once your tables grow past a few million rows.
Side-by-Side Comparison
| Dimension | UUID v4 | ULID |
|---|---|---|
| Total bits | 128 (122 random) | 128 (48 timestamp + 80 random) |
| Sortable by creation time | No — fully random | Yes — lexicographically sortable |
| String format | 550e8400-e29b-41d4-a716-446655440000 (36 chars, hyphenated hex) |
01ARZ3NDEKTSV4RRFFQ69G5FAV (26 chars, Base32) |
| Database index performance | Poor at scale — random insert pattern causes B-tree fragmentation | Good — sequential insert pattern, append-friendly |
| Reveals creation time | No | Yes (approximate, millisecond precision) |
| Case sensitivity | Lowercase convention | Case-insensitive |
| Library support | Universal — built into most languages | Good — mature libraries in major languages |
| Standardization | RFC 4122 (IETF standard) | Community spec (not an IETF RFC) |
UUID v4 — Deep Dive
UUID v4 is the most widely used UUID version, generating 122 bits of cryptographically random data (with 6 bits reserved for version and variant markers). This randomness is its core strength: there is no way to predict, guess, or infer relationships between two UUID v4 values, and no coordination is needed between systems generating IDs concurrently — any number of independent services can generate UUIDs without risk of collision.
The cost of this randomness shows up specifically in database indexing. Relational databases store primary keys in B-tree indexes, which perform best when new entries are inserted at the "end" in sorted order — exactly what happens with auto-incrementing integers. UUID v4 values are scattered uniformly across the entire 128-bit key space, so each insert lands at a random position in the index, forcing the database to split pages and rebalance the tree far more often than sequential inserts would require. At high write volumes (millions of rows per table), this becomes a measurable performance and storage overhead, well-documented in PostgreSQL and MySQL performance literature.
UUID v4 remains the right choice when unpredictability matters more than index performance — for example, session tokens, API keys, or any identifier where leaking creation-time information would be undesirable.
ULID — Deep Dive
ULID (Universally Unique Lexicographically Sortable Identifier) was created specifically to fix the index-fragmentation problem while keeping UUID-like collision resistance. The format places a 48-bit Unix timestamp (millisecond precision) in the first portion of the identifier, followed by 80 bits of randomness for uniqueness within that millisecond.
Because the timestamp comes first, ULIDs generated later in time always sort after ULIDs generated earlier — both numerically and as plain string comparison, since the Base32 encoding preserves byte-order sorting. This means database inserts using ULID as a primary key behave like sequential integer inserts: new rows append to the end of the B-tree index rather than scattering randomly, eliminating the fragmentation problem that plagues UUID v4 at scale.
ULID's string representation uses Crockford's Base32 encoding — 26 characters, all uppercase, avoiding visually ambiguous characters (no I, L, O, U) to reduce transcription errors when read aloud or typed manually. This makes ULIDs more compact and more human-friendly than hyphenated UUID strings, while still encoding the same 128 bits of information.
When to Choose UUID v4
- You need maximum unpredictability (security tokens, API keys, anything where creation-time leakage is undesirable)
- Your system already has mature UUID v4 infrastructure and the table in question has low-to-moderate write volume
- You need maximum compatibility with existing tools, ORMs, and database column types that assume standard UUID format
- Cross-system coordination isn't a concern and randomness, not sortability, is the priority
When to Choose ULID (or UUID v7)
- You're designing a new, high-write-volume table (orders, events, logs, user records) where index performance matters
- You want IDs that are naturally sortable by creation time without a separate timestamp column
- You want a more compact, case-insensitive, human-friendly string format for URLs or logs
- You're comfortable with the minor information leak of approximate creation time, which is a non-issue for the vast majority of applications
Our Verdict
For new systems, prefer a timestamp-prefixed identifier — ULID or its IETF-standardized cousin UUID v7 — over UUID v4 wherever the identifier will be a database primary key, especially for high-volume tables. The sortability directly translates into better insert performance and smaller index sizes as the table grows. Reserve UUID v4 for cases where unpredictability is a deliberate security property, such as session tokens or API secrets, where revealing creation order would be undesirable. Generate and inspect both formats with the UUID Generator and validate existing identifiers with the UUID Validator.
How UUID v7 Fits Into This Comparison
UUID v7 deserves separate attention because it's increasingly the default recommendation over both UUID v4 and ULID for new systems. Standardized by the IETF in RFC 9562 (published 2024), UUID v7 places a 48-bit Unix timestamp in milliseconds at the start of the identifier — functionally identical to ULID's approach — but keeps the traditional 36-character hyphenated hex string format and the standard UUID byte layout. This matters practically: database UUID column types, ORM UUID field validators, and API schema definitions that expect "a UUID" generally accept UUID v7 without any code changes, whereas adopting ULID often requires either a custom column type or a conversion layer at the application boundary.
PostgreSQL added native UUID v7 generation support starting with version 18, and major ORMs (Prisma, TypeORM, SQLAlchemy) added support through 2024 and 2025 as the specification stabilized. For a brand-new project today, UUID v7 is generally the safer default specifically because it requires the least amount of new tooling — you get ULID's indexing benefits while staying inside the existing UUID ecosystem that your database, ORM, and API framework already understand.
Migration Considerations for Existing Systems
Teams running production systems with UUID v4 primary keys sometimes ask whether the indexing benefit justifies migrating. In most cases it doesn't. Changing a primary key type touches every foreign key reference, every index, every API contract that exposes the ID, and every cached or logged reference to existing records — a high-risk, high-effort change for a performance benefit that primarily matters at very high write volumes (sustained inserts in the tens of millions or more). For tables already past that scale with UUID v4 keys, the more common mitigation is not a wholesale ID migration but rather partitioning the table or tuning the database's fill-factor and autovacuum settings to manage the index bloat UUID v4 causes.
The clearer case for adopting ULID or UUID v7 is new tables in an existing system, or greenfield projects where there's no migration cost to weigh against the benefit. If you're already planning a schema change that touches a high-write table's primary key for unrelated reasons, that's a reasonable moment to also switch to a sortable identifier format — but it's rarely worth triggering a migration solely for this reason on its own.