UUID v4, v7 or ULID: which identifier should you use?

Compare UUID v4, UUID v7, ULID and NanoID by sortability, database index behaviour, size and collision odds, with a plain recommendation for each use.

On this page

Use UUID v4 when you just need a unique identifier and order does not matter. Use UUID v7 when the identifier is a primary key in a database table, because it starts with the time and new rows land at the end of the index. Use a ULID when you want the same time ordering in a shorter, 26-character text form. Use a NanoID-style string for short public identifiers such as invitation links. This article explains how each one is built, what the collision odds really are, why the choice affects database speed, and what none of them should be used for.

What a UUID is

A UUID is 128 bits, written as 32 hexadecimal digits in groups of 8-4-4-4-12, for example 550e8400-e29b-41d4-a716-446655440000. Two positions carry meaning. The first digit of the third group is the version: 4 in that example. The first digit of the fourth group is the variant, and for every UUID in use today it is 8, 9, a or b. The standard is RFC 9562, published in 2024, which replaced RFC 4122 and added versions 6, 7 and 8 to the older ones.

A GUID, the Microsoft term, is the same thing, sometimes written in capitals and in braces.

UUID v4: 122 random bits

A v4 UUID is random apart from six fixed bits, four for the version and two for the variant. That leaves 122 random bits, or about 5.3 × 10^36 possible values. Generated with a good random source, such as the cryptographic generator built into a browser, it is the default choice because every language, database and framework supports it.

What the collision odds really are

For n random values drawn from N possibilities, the chance that any two match is about 1 − e^(−n(n−1) / 2N), the "birthday" estimate. With N = 2^122, computed with high-precision arithmetic:

UUIDs generatedChance that at least two are equal
1 millionabout 9.4 × 10^-26
1 billionabout 9.4 × 10^-20
1 trillionabout 9.4 × 10^-14
1 quadrillionabout 9.4 × 10^-8
1 quintillion (10^18)about 9 percent

The odds reach 50 percent only after about 2.7 × 10^18 UUIDs. At a billion per second that would take roughly 86 years. In practice a duplicate is not what goes wrong. What goes wrong is a poor random source, such as Math.random() in old libraries, or a virtual machine image that has been cloned together with its random state. The estimates assume a good generator, so keep a unique index in your database anyway: it is cheap and it turns an astronomically unlikely event into a clean error.

UUID v7: time first

A v7 UUID starts with a 48-bit timestamp in milliseconds since 1970, then 4 bits of version, 2 bits of variant and 74 bits that are random or a counter. The example in the standard is 017f22e2-79b0-7cc3-98c4-dc0c0c07398f. Its first 12 digits, 017f22e279b0, are 1,645,557,742,000 milliseconds after the epoch, which is 22 February 2022, 19:22:22 UTC. The 48-bit field lasts until the year 10889.

Because the timestamp comes first, v7 values sort in creation order, as plain text or as binary. IDs made within the same millisecond need a tiebreak. The generator below counts the random part up by one, so a batch comes out in order. A batch made on 13 October 2026 looked like this:

01a1355c-a15a-711d-b937-2da9e4688fc1
01a1355c-a15a-711d-b937-2da9e4688fc2
01a1355c-a15a-711d-b937-2da9e4688fc3

A batch of v4 values, for comparison, shares nothing:

ecd64af2-9621-42d4-b5a5-7cc064b76f66
cb382b5b-a904-4799-96c0-d610b88d93bf
2817867b-4336-40ad-ae08-96381f6567eb

ULID: the same idea in 26 characters

A ULID has a 48-bit millisecond timestamp and 80 random bits, written in 26 characters of Crockford base32, which leaves out the letters I, L, O and U so they are hard to misread. It sorts alphabetically by time. The first 10 characters are the time: 01ARZ3NDEK is 1,469,922,850,259 milliseconds, which is 30 July 2016. It is not a UUID, though it has the same 128 bits and can be stored in a UUID column. Its main advantages are the shorter text and the lack of hyphens. Its main disadvantage is that fewer tools and libraries know it.

NanoID-style strings

NanoID is a random string over a 64-character URL-safe alphabet. The default of 21 characters carries 21 × 6 = 126 bits, about as much as a UUID, in 21 characters. It has no time ordering and no version marker, so it suits short public identifiers: a share link, an invitation code, a short order reference. Do not use it as a primary key when write speed matters, for the reason in the next section.

Why the choice affects database speed

Most databases keep a primary key in a B-tree index, a sorted tree of pages. A sequential key, like an auto-increment number or a v7 UUID, always inserts at the right-hand edge. The same few pages stay in memory, fill up and are not touched again. A random key, like v4, inserts at a random place in the tree every time. Once the index is bigger than memory, each insert may need a page read from disk, pages split in the middle and are left half full, and the index grows larger than it needs to. This is the "index locality" problem. In engines that store a table in primary key order, such as MySQL's InnoDB, the whole table is affected, not only the index.

The effect depends on size. A table of a few thousand rows will not notice. A table of hundreds of millions of rows with a v4 key often does. The ordering also helps in daily work: the newest rows are next to each other, and "the last 100 records" is a cheap range scan.

Support has grown. PostgreSQL has long offered gen_random_uuid() for v4, and PostgreSQL 18 added a built-in uuidv7(); with older versions you generate v7 in the application or with an extension. Store UUIDs in the database's native 16-byte type where there is one, not as 36 characters of text.

What none of them are for

  • Secrets. A UUID is an identifier, not a password. v7 and ULID reveal when the ID was made. Within one millisecond, a counter makes neighbouring IDs guessable from each other. A v4 UUID has 122 random bits, but treat it as an identifier anyway. For a password or reset token use the password generator; the article on what makes a strong password explains why length and randomness matter.
  • Authorisation. Knowing an ID must never be enough to read the record. Check permissions on every request.
  • Business logic. Do not read the timestamp out of an ID to decide anything; store a real created_at column. Reading it for debugging is fine.

Which one to pick

You needUse
A unique ID and order does not matterUUID v4
A primary key in a busy tableUUID v7
Time-sortable, shorter text, no hyphensULID
A short public code in a URLNanoID-style string
A secret, a password or a tokenNone of these: use a password generator

Generating them

  1. Open the UUID generator and choose a Type: UUID v4 (random), UUID v7 (time-ordered), ULID or NanoID style.
  2. Set How many, from 1 to 1000. For NanoID set the Length, from 1 to 128.
  3. For UUIDs, tick Uppercase, switch Hyphens off, or add Braces { } for the Windows GUID style. The ID itself does not change, only how it is written.
  4. Press "Copy all" for one ID per line, or download a TXT file, or a CSV file with an id header for a spreadsheet or a seed script. "Generate again" makes a fresh batch.

The random numbers come from your browser's cryptographic generator, and nothing leaves the page. The first 12 hexadecimal digits of a v7 UUID are a millisecond timestamp: convert them to a decimal number and the timestamp converter shows the date, and seconds versus milliseconds explains the units.

Common mistakes

  • Comparing IDs as text without normalising. A UUID is the same in upper and lower case, with or without hyphens or braces. Store one canonical form, normally lower case with hyphens.
  • Using v4 as a primary key in a very large table and then wondering why inserts are slow. Consider v7.
  • Sorting v4 values and expecting creation order. They carry none.
  • Skipping the unique index. Randomness gives odds, not a guarantee.
  • Using a weak random function. Take the ID from a cryptographic source.

A short checklist

Default to UUID v4, switch to v7 for primary keys, pick a ULID or NanoID only if you need the text form, keep a unique index, store real timestamps in their own columns, and never treat an identifier as a secret.

Tools mentioned in this article

Back to the blog