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 carries 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. That randomness is its main strength. There's no way to predict, guess, or infer relationships between two UUID v4 values, and no coordination is needed between systems generating IDs at the same time. Any number of independent services can generate UUIDs without collision risk.
The cost of that randomness shows up in database indexing. Relational databases store primary keys in B-tree indexes, which perform best when new entries land at the "end" in sorted order, exactly what happens with auto-incrementing integers. UUID v4 values scatter uniformly across the entire 128-bit key space, so each insert lands at a random position in the index. The database ends up splitting pages and rebalancing the tree far more often than sequential inserts would require. At high write volumes, millions of rows per table, this turns into measurable performance and storage overhead, and it's well documented in PostgreSQL and MySQL performance literature.
UUID v4 still makes sense when unpredictability matters more than index performance, for session tokens, API keys, or any identifier where leaking creation-time information would be a problem.
ULID: Deep Dive
ULID (Universally Unique Lexicographically Sortable Identifier) was built specifically to fix the index-fragmentation problem while keeping UUID-like collision resistance. The format places a 48-bit Unix timestamp (millisecond precision) at the front 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 ones generated earlier, both numerically and as plain string comparison, since the Base32 encoding preserves byte-order sorting. That 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 instead of scattering randomly, which removes the fragmentation problem that plagues UUID v4 at scale.
ULID's string representation uses Crockford's Base32 encoding, 26 characters, all uppercase, skipping visually ambiguous characters (no I, L, O, U) to cut down transcription errors when read aloud or typed by hand. That makes ULIDs more compact and easier on the eyes 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 would be undesirable.
- Your system already has mature UUID v4 infrastructure and the table in question sees low-to-moderate write volume.
- You need maximum compatibility with existing tools, ORMs, and database column types built around the standard UUID format.
- Cross-system coordination isn't a concern, and randomness matters more to you than sortability.
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 sort naturally 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 fine with the minor information leak of approximate creation time, which is a non-issue for most applications.
Our Verdict
For new systems, lean toward a timestamp-prefixed identifier, ULID or its IETF-standardized cousin UUID v7, over UUID v4 wherever the identifier will serve as a database primary key, especially on high-volume tables. The sortability translates directly into better insert performance and smaller index sizes as the table grows. Save UUID v4 for cases where unpredictability is a deliberate security property, like session tokens or API secrets, where revealing creation order would be a liability. 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 its own attention because it's becoming 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 the same approach ULID takes, but it keeps the traditional 36-character hyphenated hex string format and the standard UUID byte layout. That matters in practice: database UUID column types, ORM UUID field validators, and API schema definitions that expect "a UUID" generally accept UUID v7 without any code changes, while adopting ULID often means a custom column type or a conversion layer at the application boundary.
PostgreSQL added native UUID v7 generation starting with version 18, and major ORMs (Prisma, TypeORM, SQLAlchemy) added support through 2024 and 2025 as the spec stabilized. For a brand-new project today, UUID v7 is generally the safer default because it needs the least new tooling. You get ULID's indexing benefits while staying inside the UUID ecosystem 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. That's a high-risk, high-effort change for a performance benefit that mainly 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 fix isn't a wholesale ID migration but 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 with 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 switch to a sortable identifier format too. It's rarely worth triggering a migration for this reason alone.