Guides

ULID vs UUID: Which ID Should You Use?

Marcus Brennanยทยท8 min read

The first time I saw a ULID in a production database, I assumed someone had fat-fingered a UUID โ€” it was the right kind of length but all the hyphens were gone and the characters looked off. It turned out to be a deliberate choice, and a reasonable one. So which should you actually use? The answer comes down to a single property, and once you see it the rest falls into place.

Same size, different ambitions

A UUID and a ULID are both 128 bits. The difference is entirely in how those bits are arranged.

A random UUID โ€” version 4, the kind most people mean when they say "UUID" โ€” is essentially 122 bits of randomness with a few version markers. Two UUIDs generated a millisecond apart have no relationship to each other whatsoever. That's a feature for unguessability and a headache for anything that wants order. If you need the background on the format itself, what a UUID is covers the versions and layout.

A ULID (Universally Unique Lexicographically Sortable Identifier) splits those 128 bits into two halves: a 48-bit timestamp (milliseconds since the Unix epoch) followed by 80 bits of randomness. It's rendered as a 26-character Crockford base32 string โ€” shorter than a UUID's 36 characters, with no hyphens, and safe to drop straight into a URL.

The one real difference: ordering

Because the timestamp sits at the front, ULIDs sort chronologically when you sort them as plain text. Generate a batch over a few seconds and their string order is also their creation order. Random UUIDs give you no such thing โ€” sort them and you get noise.

Think of two shipping companies numbering their parcels. One assigns a random code to every package; to find everything that shipped on Tuesday, a worker has to scan the entire warehouse. The other prefixes each code with the date and time, so the day's parcels naturally cluster together on the shelf and the newest ones are always at the end of the row. The parcels are identical; the numbering scheme is what makes one warehouse searchable and the other a scavenger hunt.

Where ULIDs win

That ordering is not a cosmetic nicety โ€” it has real performance consequences as a database primary key. When you insert rows keyed by random UUIDs, each new key lands at an unpredictable spot in the index, scattering writes across the whole structure, splitting pages, and thrashing the cache. Time-ordered keys append near the end of the index instead, which is dramatically friendlier to the B-tree. This is the exact problem laid out in UUID database primary key best practices and the broader UUID vs auto-increment trade-off.

ULIDs also give you a free created_at ordering with no extra column, which is handy for event logs and feeds, and their compact base32 form is pleasant in URLs. You can decode the embedded moment with a Unix timestamp converter by reading the first 48 bits.

Where ULIDs quietly backfire

The same timestamp that makes a ULID sortable also makes it talk. Every ULID openly reveals when its record was created, and because consecutive IDs are close together, they are easier to enumerate and correlate than random ones. For a public-facing identifier โ€” a share link, a password-reset token, anything that should be hard to guess โ€” that's a liability, and a random UUID v4 or a dedicated random token from a random string generator is the safer call. The same reasoning that makes NanoID a good fit for short, random public IDs argues against ULID there.

ULID is also a community spec rather than an IETF standard, so support across databases, ORMs, and languages is patchier than for UUID.

UUID v7 changed the math

Here's the twist that reframes the whole comparison: the UUID standard caught up. RFC 9562 (2024) added UUID version 7, which is itself a time-ordered UUID โ€” a millisecond timestamp up front, randomness after. In other words, you can now get ULID's index-friendly, sortable behavior while still emitting a genuine UUID that every UUID-aware tool already handles. The UUID v4 vs v7 comparison walks through exactly what v7 changes.

That makes the practical decision simpler than the ULID-vs-UUID framing suggests. You can generate both v4 and v7 with the UUID generator and compare them directly.

The verdict

  • Need unguessable, random, standard IDs? Use UUID v4.
  • Need time-ordered keys that index well, and you're starting fresh? Use UUID v7 โ€” same benefit as ULID, inside the standard.
  • Specifically want the short base32 string, or already standardized on it? ULID is a fine choice; just never use it where the leaked timestamp or easy enumeration would hurt you.

The headline question is "ULID vs UUID," but for most new systems the honest answer is a third option: reach for UUID v7 and get the best of both.

Try the tools

Frequently Asked Questions

What is the difference between a ULID and a UUID?

Both are 128-bit identifiers, but they're laid out differently. A random UUID (v4) is 122 bits of pure randomness, so two IDs created a millisecond apart have no relationship and sort randomly. A ULID puts a 48-bit millisecond timestamp first, then 80 random bits, so ULIDs created in order also sort in order. ULIDs also render as a shorter, URL-safe 26-character base32 string instead of the 36-character hyphenated UUID form.

Is a ULID better than a UUID for a database primary key?

Often, yes, for write performance. Random UUIDs scatter inserts across a clustered index, causing page splits and poor cache locality; time-ordered IDs like ULID append near the end of the index, which is much friendlier. But before reaching for ULID, check whether UUID v7 fits โ€” it's a time-ordered UUID that gives the same index benefit while staying inside the standard UUID format your tools already understand.

Does a ULID leak information?

Yes โ€” by design it embeds its creation time, and you can read that timestamp straight out of the first 48 bits. That's useful for debugging and sorting, but it means a ULID reveals when the record was made and makes IDs easier to enumerate or correlate. For public-facing identifiers where unguessability matters, a fully random UUID v4 (or a random token) is the safer choice.

Are ULIDs guaranteed to be unique?

In practice, effectively yes, but not through a central registry. Within the same millisecond, ULIDs rely on 80 bits of randomness (or a monotonic counter in strict implementations) to avoid collisions, and across different milliseconds the timestamp itself separates them. As with UUIDs, uniqueness is probabilistic โ€” the odds of a collision are vanishingly small at normal volumes, not mathematically impossible.

Should I use ULID or UUID v7?

If you want time-ordered IDs and you're starting fresh, UUID v7 is usually the better pick: it delivers the same index-friendly, time-sortable behavior as ULID but is a real UUID, so databases, ORMs, and libraries that expect UUID columns handle it without special support. Reach for ULID mainly when you specifically want its shorter base32 string form or you're in an ecosystem that already standardized on it.

MB

Marcus Brennan writes for CodeUtilityKit, where the team builds free, privacy-first developer tools that run entirely in your browser. Every guide is written and reviewed by developers who use these tools daily.