Same job, different shape
Both are 128-bit identifiers with negligible collision risk. The difference is structure: UUIDv4 is 122 random bits; a ULID is 48 bits of millisecond timestamp + 80 random bits, encoded as 26 uppercase alphanumeric characters (Crockford base32). That timestamp changes everything about how each behaves in a database.
The index locality problem
Random UUIDs inserted into a B-tree index land at random leaf positions: every insert touches a random page, pages split unevenly, the cache hit rate drops, and on very large tables the index fragments. ULIDs are time-ordered, so inserts append to the right edge of the index — sequential-ish writes, better cache behavior, and naturally sorted by creation time. This is why event stores, log aggregators, and DynamoDB-style systems (KSUIDs, Snowflake IDs — same idea) use time-ordered IDs.
Trade-offs
| UUIDv4 | ULID | |
|---|---|---|
| Sortability | meaningless order | creation order |
| Index behavior | random writes | append-mostly |
| Unpredictability | opaque | embeds creation time |
| Length (text) | 36 chars | 26 chars |
| Case/format | hex + dashes | Crockford base32, canonically uppercase |
| Standardization | RFC 4122 everywhere | community spec, many impls |
When the timestamp leaks
ULIDs expose creation time — fine for a comment ID, questionable for an order ID when volume is business-sensitive (competitors can estimate your throughput from IDs), and outright wrong for capability URLs or reset tokens, which must be unguessable. The random portion is 80 bits — enough for collision resistance at normal scale, but not for unguessability against a targeted guesser; use random UUIDs or longer tokens there.
Monotonicity within a millisecond
Two ULIDs generated in the same millisecond must increment their random part to stay ordered — implementations differ (some offer a monotonic factory), so concurrent generators on one host need a coordination choice. Also note the 48-bit timestamp overflows in 10889 AD — someone else's problem.
Generate and compare both formats locally in the ULID Generator and UUID Generator.