UUID vs ULID: What's the Difference?
When you need unique IDs for users, orders, payments, or API resources, an auto-incrementing integer isn't always the right fit — it leaks how many rows you have, it's awkward to generate across multiple services without coordination, and it doesn't work well once you have more than one database writing IDs. UUIDs and ULIDs are two common alternatives, and developers increasingly have to pick between them, or between different UUID versions.
This isn't a case where one format is simply better — the right choice depends on your database, whether IDs need to sort by creation time, how you store them, and your ecosystem's tooling. This article covers what each format is, how they compare, and where each tends to fit, using Kravoy's UUID / ULID Generator to see real examples of each.
What Is a UUID?
UUID stands for Universally Unique Identifier. It's a 128-bit value, and RFC 9562 (the current UUID specification) describes it as "intended to guarantee uniqueness across space and time." A UUID is normally written as 32 hexadecimal digits split into five groups by hyphens — 36 characters in total:
550e8400-e29b-41d4-a716-446655440000
The point of a UUID is that any system can generate one without checking in with a central authority, and with a correctly implemented generator, the odds of two systems producing the same value are negligible. Not every UUID is generated the same way — there are several "versions," and the two you'll run into most today are v4 and v7.
What Is UUID v4?
UUID v4 is the version most people mean when they just say "a UUID." Example:
550e8400-e29b-41d4-a716-446655440000
A v4 UUID isn't entirely random — 6 of its 128 bits are fixed to mark the version and variant (so parsers can recognize it as a v4 UUID), leaving 122 bits genuinely random. In the example above, the 4 right after the second hyphen is the version marker, and the a at the start of the next group is part of the variant marker.
Because those 122 bits are random, consecutive v4 UUIDs have no relationship to each other — there's no way to tell which of two was generated first just by looking at them. That simplicity is why every generator supports v4, but it also means v4 UUIDs don't sort in any meaningful order.
What Is UUID v7?
UUID v7 is a newer version built specifically to address that gap. Per RFC 9562, its 128 bits are laid out as a 48-bit Unix millisecond timestamp, followed by the 4-bit version marker, then random data, the 2-bit variant marker, and more random data — 74 bits of randomness in total, versus v4's 122. Here's a UUID v7 for a specific moment in time, 2026-09-24 10:15:30.000 UTC, computed correctly for that timestamp so the structure is genuine, not invented:
01a0d2e9-c1d0-7180-a3f7-e1c9a0b1c2d3
The first 12 hex characters (01a0d2e9-c1d0, ignoring the hyphen) encode that exact millisecond; the 7 right after marks it as version 7. Because the timestamp sits at the front of the value, sorting UUID v7s as raw bytes or as text also sorts them by creation time — which is exactly what RFC 9562 calls out as the purpose: implementations "that require sorting (e.g., database indexes) sort as opaque raw bytes without the need for parsing or introspection." The RFC recommends using v7 over the older time-based v1 where possible.
One nuance: sorting by UUID v7 is accurate to the millisecond, not perfectly ordered within it — two v7 UUIDs generated in the same millisecond sort essentially randomly relative to each other unless the generator implements a monotonic counter, which the spec allows but doesn't require. And UUID v7 doesn't make a database "faster" as a blanket claim; it can be more database-friendly where time-ordered identifiers help index locality (next section), but that depends on your database and access patterns.
What Is a ULID?
ULID stands for Universally Unique Lexicographically Sortable Identifier. Example:
01ARZ3NDEKTSV4RRFFQ69G5FAV
(This is the reference example from the ULID specification itself — decoding its timestamp component gives 2016-07-30T23:54:10.259Z, confirming it's a real, correctly structured example rather than an arbitrary string.)
Like a UUID, a ULID is 128 bits: 48 bits of millisecond timestamp (same range as UUID v7) plus 80 bits of randomness — more random bits than either UUID version. But its text representation is different: instead of hexadecimal, a ULID is encoded in Crockford's Base32, giving a 26-character string using only the digits and letters that encoding defines — it deliberately excludes I, L, O, and U to avoid visual confusion (with 1, 1, 0, and to avoid spelling accidental words). No hyphens, and it's shorter than a UUID's 36 characters for the same 128 bits of data, simply because Base32 packs more bits per character than hex does.
Because the timestamp is encoded first, ULIDs sort lexicographically (as plain text, character by character) in creation order — hence "sortable" being in the name. Like UUID v7, that ordering is accurate to the millisecond; the specification defines an optional monotonicity scheme (incrementing the random portion by one within the same millisecond) for implementations that need strict same-millisecond ordering, but a basic ULID generator isn't required to implement it.
UUID vs ULID: Quick Comparison
| Feature | UUID | ULID |
|---|---|---|
| Size | 128-bit | 128-bit |
| Typical text format | Hexadecimal | Crockford Base32 |
| Typical string length | 36 characters (with hyphens) | 26 characters |
| Time-sortable | Depends on version (v7: yes; v4: no) | Yes, by design |
| Random UUID | v4 | Random component + timestamp |
| Modern time-ordered option | UUID v7 | Native design |
| Ecosystem support | Very broad, long-established | Growing, good library coverage |
| Database use | Very common | Increasingly common |
A shorter string isn't automatically an advantage — it matters if you're storing millions of rows as text, or embedding IDs somewhere space-constrained, but it's a minor factor if you store IDs as fixed-width binary regardless of format (see below).
UUID v4 vs UUID v7 vs ULID
| UUID v4 | UUID v7 | ULID | |
|---|---|---|---|
| Random bits | 122 | 74 | 80 |
| Timestamp component | No | Yes (48-bit ms) | Yes (48-bit ms) |
| Sortable by creation time | No | Yes, to millisecond resolution* | Yes, to millisecond resolution* |
| Text representation | Hex | Hex | Base32 |
| Text length | 36 | 36 | 26 |
| Ecosystem maturity | Very high | High and growing | Growing |
| Main advantage | Simplicity, universal support | Time ordering within the existing UUID format | Compact, sortable, built for this from the start |
* Strict ordering within the same millisecond depends on whether the specific generator implements a monotonic counter — not guaranteed by the base UUID v7 or ULID definitions.
Why Does Sortability Matter?
This comes down to how database indexes work. A classic auto-incrementing key always inserts at the end of its index — 1, 2, 3, 4 — a predictable, append-only pattern that B-tree indexes (used by PostgreSQL, MySQL/InnoDB, SQL Server, and others) handle efficiently. A fully random key — a UUID v4 like 9f3a... then 2a71... then e7c4... — inserts at essentially random positions instead, which can mean more page splits and less predictable locality as the table grows.
Time-ordered identifiers like UUID v7 and ULID sit closer to that append-only pattern, which is why they're often described as more database-friendly for large, high-insert tables. How much it actually matters depends on your database, storage engine, index type, and table size — it's a real, well-documented effect, not a myth, but there's no universal multiplier to quote. If insert performance and index bloat are a real concern, test it against your own schema and data volume.
UUID vs ULID for Database Primary Keys
Support and conventions differ across databases:
- PostgreSQL has a native
uuidcolumn type, stored efficiently as 16 bytes regardless of how you write the literal. There's no native ULID type, so a ULID is typically stored as its 26-character text form, or converted to a UUID-shaped 16-byte value for auuidcolumn. - MySQL has no dedicated UUID type before MySQL 8 introduced UUID-handling functions; a UUID is commonly stored as
CHAR(36), or converted to a compactBINARY(16)to keep index size down. - SQL Server has a native
uniqueidentifiertype, but its default sort order for that type does not match the byte order most UUID generators produce, which is a common source of unexpected index behavior specifically on that platform — worth checking against current documentation for your version if it matters to you.
Across all of them, the practical point is the same: storing an ID as a 36-character (or 26-character) string is not the same as storing it as its compact binary form, and the difference affects index size directly.
Should You Store UUID as String or Binary?
A CHAR(36) UUID column is simple: it's human-readable in query results, trivial to copy into a support ticket or log line, and works with zero extra application code. It also takes more storage and index space than necessary, since 36 bytes of text encode the same 128 bits that fit in 16 bytes of binary.
Storing the compact 16-byte binary form instead shrinks both the column and every index built on it, which matters more as a table grows into the tens or hundreds of millions of rows. The tradeoff is that raw binary isn't readable in a query result or a log without converting it back, and your application or ORM needs to handle that conversion consistently everywhere the ID is used.
Neither approach is correct for every project. A smaller internal service with a modest row count usually isn't worth the added complexity of binary storage; a large, high-throughput table is a more natural candidate for it. This is a genuine engineering tradeoff to weigh against your own scale, not a default to apply everywhere.
Are UUIDs Really Unique?
Not in the sense of a mathematical guarantee — in the sense of a collision probability small enough to treat as effectively zero when the generator is implemented correctly. A v4 UUID has 122 random bits. Using the standard birthday-bound approximation, you would need to generate roughly 2.7 × 10¹⁸ UUIDs — about 2.7 billion billion — before the probability of ever seeing a single collision reaches 50%. Generating a continuous 1 billion UUIDs every second, that threshold would take roughly 86 years to reach. That's not a guarantee that a collision can never happen, but it is negligible for essentially any real application, and it depends on the generator actually using a proper source of randomness — a broken or predictable random number generator would undermine all of this.
Are ULIDs Unique?
The same logic applies, with one structural difference: because 48 of a ULID's 128 bits are a timestamp shared by everything generated in that same millisecond, the meaningful collision risk is scoped to "two ULIDs generated in the exact same millisecond with the same 80-bit random value" rather than across the entire ID space at once. That's still an enormous number of possibilities per millisecond, so the practical risk is negligible for ordinary use — but it's also why the ULID specification separately defines an optional monotonic mode (incrementing the random portion within a millisecond) for cases that specifically need to guarantee no same-millisecond collisions at high generation rates, rather than relying on randomness alone.
UUID vs ULID for APIs
A typical API response might return:
{
"id": "01M39EKGEG9WN9S0F3PV85Y6KQ",
"name": "Example Product"
}
or
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Example Product"
}
Either format has real advantages as a public resource identifier: it doesn't reveal a simple sequential count of your records (unlike /products/4821, which tells a competitor roughly how many products you have and how fast that number is growing), and services can generate IDs independently without coordinating with a central counter.
One thing worth being explicit about: using a UUID or ULID instead of a sequential integer does not, by itself, provide security or access control. An unguessable ID can make casual enumeration harder, but if a resource genuinely needs to be restricted to certain users, that has to be enforced with real authorization checks — not by relying on an ID being hard to guess.
UUID vs ULID for URLs
/products/550e8400-e29b-41d4-a716-446655440000
/products/01ARZ3NDEKTSV4RRFFQ69G5FAV
A ULID is shorter in its textual form, which is a genuine, visible difference in a URL. It's not a meaningful SEO factor on its own, though — search engines handle both formats fine as path segments, and URL length in this range has no established, direct ranking effect. If a shorter URL is worth having, it's for the person reading, copying, or typing it, not for search rankings.
UUID vs ULID: Which Should You Choose?
UUID (v4) may make sense when:
- your ecosystem, ORM, or database already has strong native UUID support
- compatibility with existing systems and tools matters more than sort order
- you want the most widely recognized identifier format, with the broadest library and tooling support
UUID v7 may make sense when:
- you want UUID's format and ecosystem compatibility
- time-ordering is useful for your access patterns or debugging
- you care about the index-locality characteristics described above
ULID may make sense when:
- lexicographic (plain text) sort order is directly useful to you
- a more compact textual ID is genuinely valuable in your context
- your stack already has solid ULID library support
None of these is a universal winner. The right choice depends on your application's requirements and how your specific database behaves under your specific workload.
How to Generate UUIDs or ULIDs With Kravoy
Kravoy's UUID / ULID Generator creates all of the formats covered here, entirely in your browser:
- Open the UUID / ULID Generator.
- Choose the ID type: UUID v4, UUID v7, ULID, or NanoID.
- Set how many you need — one, or up to 1,000 at once.
- Optionally strip hyphens, force upper- or lowercase, or wrap each value in quotes, depending on where you're pasting them.
- Copy the result, or download it as a
.txtor.csvfile for bulk use in seed data or tests.
Common Mistakes
- Assuming UUID or ULID implies encryption or security. Both are identifier formats, not security mechanisms.
- Exposing internal assumptions based on ID structure. Don't build logic elsewhere that assumes a particular ID format will never change.
- Using random (v4) UUIDs as a primary key without considering index behavior on a large, high-insert table, where a time-ordered format may behave better.
- Storing large textual IDs without weighing storage and index cost at scale — fine for a small table, worth reconsidering for a very large one.
- Assuming ULID's monotonic mode is automatic. Strict same-millisecond ordering requires a generator that specifically implements it.
- Assuming UUID v7 is universally faster than v4. It can help with index locality in some workloads; it isn't a guaranteed performance win in every setup.
- Treating an unguessable ID as a substitute for authorization checks. It isn't one.
Frequently Asked Questions
What is a UUID?
A 128-bit identifier, usually written as 36 hexadecimal characters, designed to be generated independently by any system with a negligible chance of colliding with another.
What is a ULID?
A 128-bit identifier combining a 48-bit millisecond timestamp with 80 bits of randomness, written as a 26-character string that sorts in creation order as plain text.
Is ULID better than UUID?
Not universally — it's a different set of tradeoffs. ULID is more compact and sortable by default; UUID (particularly v4) has broader, longer-established ecosystem support. UUID v7 narrows the gap by adding time-ordering to the standard UUID format.
What is the difference between UUID v4 and UUID v7?
UUID v4 is 122 bits of randomness with no time component, so it has no inherent order. UUID v7 replaces part of that randomness with a 48-bit timestamp, so v7 values sort chronologically to millisecond resolution.
Is ULID sortable?
Yes, by design — its timestamp is encoded first, so ULIDs sort in creation order as plain text, to millisecond resolution. Strict ordering within the exact same millisecond needs a generator that implements the optional monotonic mode.
Are UUIDs unique?
Not by mathematical guarantee, but with a correctly implemented random generator, the collision probability is negligible for real-world use — computing it directly, you'd need on the order of 10¹⁸ v4 UUIDs before a collision becomes as likely as not.
Should I use UUID or ULID as a database ID?
It depends on your database, table size, insert patterns, and whether sort order matters to you. There's no single correct answer independent of those factors.
Is UUID secure?
A UUID being hard to guess isn't the same as a resource being secure. Using UUIDs doesn't remove the need for real authorization checks on anything that shouldn't be publicly accessible.