UUID versions explained

Eight versions exist and three of them matter. Which one to reach for, what each gives away, and the measurements behind both answers.

Free toolUUID GeneratorVersion 4, 7, 1, 5 and nil identifiers, one or ten thousand at a time, from your own browser. Reads a UUID back apart too.Open the generator

The short answer

Version 4 for anything, version 7 for a database primary key. Version 4 is 122 random bits and nothing else, which is what you want when an identifier only has to be unique. Version 7 puts a millisecond timestamp at the front, so a column of them sorts and inserts in order, which is what a B-tree index wants.

Version 5 is the third one worth knowing: it is a hash, so the same input always produces the same identifier. Version 1 is the format everything used before 2005 and the only reason to choose it now is that something else demands it. The rest are history or blank space.

VersionWhat is inside itSorts?Use it when
4122 random bits.NoThe default. Anything that just has to be unique.
748-bit millisecond clock, 12-bit counter, 62 random bits.YesA primary key, or anything you will sort by creation time.
5SHA-1 of a namespace and a name.NoThe same input must always give the same identifier.
1100-nanosecond clock since 1582, plus a node field.NoSomething you do not control requires it.
6The same fields as version 1, reordered so they sort.YesYou already have version 1 data and need it to sort.
3MD5 of a namespace and a name.NoNever, for new work. It is version 5 with a worse hash.
2A version 1 with half the clock replaced by a POSIX id.NoNever. It was only ever specified inside DCE.
8Whatever you put in it.DependsYou are defining your own format and want a version number for it.

What we measured

Three things, all run in Chrome 152 on an Apple M1 Max with 32 GB, on macOS 26.6.2, on 2 September 2026. The generator being measured is the one on the UUID generator page, driven through its own controls rather than through a copy of the code, so what is timed is the whole path from pressing the button to the identifiers being on screen.

1. Version 7 does not sort unless it carries a counter

This is the part that gets left out. A version 7 UUID is a millisecond timestamp followed by random bits, so two made inside the same millisecond sort against each other at random. On a modern machine that is not an edge case: the run below produced 100,000 identifiers inside two milliseconds, which is about 50,000 per millisecond.

RFC 9562 §6.2 offers a fix, and method 1 is to use the twelve rand_a bits as a counter that increments within each millisecond. The table is the same 100,000 identifiers generated both ways, counting adjacent pairs that came out in the wrong lexicographic order.

rand_a holdsPairs out of orderDuplicatesTime for 100,000
Random bits50,011 of 99,999018.0 ms
A counter0 of 99,999025.6 ms

Half the pairs, which is exactly what a coin toss predicts once the timestamps are equal. The counter costs 7.6 ms over 100,000 identifiers, or 76 nanoseconds each, and it is the difference between a version that does its one job and a version that does not. Check whichever library you are using: not all of them implement it, and the failure is invisible until you sort something.

2. Where the randomness actually comes from, tested

A full collision cannot be reached in a browser, so the generator was tested on a deliberately narrow field instead: the first 32 random bits of its own output, drawn until one repeated, 200 times over. If those bits are uniform, the birthday problem says the first repeat should turn up after about sqrt(pi / 2 × 2^32) draws.

MeasureValue
Trials200
Predicted first repeat82,137 draws
Measured mean85,399 draws
Standard deviation43,722
Standard error of the mean3,092
Difference from prediction1.06 standard errors

Within one standard error, which is the answer you want. Push the same arithmetic out to the 122 random bits a real version 4 carries and you get about 2.7 quintillion identifiers before a collision reaches even a one in a billion chance. That is the number people quote, and this is the check that the generator in front of you is actually drawing from the space the number assumes.

The caveat matters more than the result. The maths holds only for a cryptographic source. A version 4 built on Math.random looks identical, passes this same test at 32 bits, and still collides in production, because the state behind it is far smaller than 122 bits. That is where real duplicate-ID incidents come from, not from the birthday bound.

3. How long a batch actually takes

Median of nine presses, measured from the click to the identifiers being rendered. Every version fills one buffer from a single call to the platform's random number generator rather than calling it once per identifier, which is where the numbers come from.

Version11,00010,000
40.1 ms3.0 ms23.4 ms
70.1 ms2.6 ms24.3 ms
10.1 ms2.9 ms27.2 ms

Version 1 is the slowest because its 60-bit clock passes what a JavaScript number can hold exactly, so every identifier costs a handful of BigInt operations. It is 3.8 ms over ten thousand, which is a reason to know the number and not a reason to avoid the version.

What each version gives away

An identifier is often visible in a URL, a log line or an API response, so it is worth knowing what a stranger can read out of one. Paste any UUID into the reader at the bottom of the generator and it will tell you all of this.

VersionA stranger can readMatters when
4Nothing at all.Never.
7The millisecond it was created.Row creation times are sensitive, or the count of them is.
1The moment, to 100 nanoseconds, and the node field.Always. See below.
5Nothing directly, but the same input gives the same output, so a guessable name is a guessable identifier.The name is an email address or an account number.

The version 1 node field, measured on a real service

A version 1 UUID ends with 48 bits called the node. The 1980s intent was the machine's MAC address, which is how a version 1 was made unique across machines. RFC 9562 §6.10 now says to use random data instead, with the low bit of the first octet set to 1 to mark it as not a real hardware address.

Whether a given generator does that is checkable, and worth checking. Two separate batches of ten version 1 identifiers were taken from uuidtools.com on 2 September 2026. Every one of the twenty ended in the same node, 32:50:96:b3:9f:47, and its first octet is 0x32, whose low bit is 0. That is a real MAC address, and it is theirs: the server's, not yours.

Nothing about that is malicious, and it is a fair reading of the original specification. It is worth knowing for two reasons. The uniqueness property version 1 was built on does not apply when every identifier shares one node, and any version 1 from a website carries a stable fingerprint of the machine that made it. A browser cannot read a MAC address at all, which is why the generator here uses a random node with the multicast bit set, and why the reader will tell you which kind you are holding.

Making one in your own code

The one line per language, so you do not need a website in a script. Version 4 first, then version 7 where the standard library or the common library has it.

LanguageVersion 4Version 7
JavaScriptcrypto.randomUUID()uuid package, v7()
Pythonuuid.uuid4()uuid.uuid7() from 3.14, or uuid6 before that
JavaUUID.randomUUID()com.fasterxml.uuid.Generators.timeBasedEpochGenerator()
C#Guid.NewGuid()Guid.CreateVersion7() from .NET 9
Gouuid.New()uuid.NewV7()
RustUuid::new_v4()Uuid::now_v7()
PHPUuid::uuid4(), ramsey/uuidUuid::uuid7()
RubySecureRandom.uuidSecureRandom.uuid_v7 from 3.3

And in a database

DatabaseThe callNotes
PostgreSQLgen_random_uuid(), uuidv7()Version 4 is built in from 13, version 7 from 18. Store it as uuid.
SQL ServerNEWID(), NEWSEQUENTIALID()NEWID is version 4. NEWSEQUENTIALID sorts, but only within one server restart, and it is not a standard UUID version.
MySQL and MariaDBUUID()Version 1, with the server's node. Store it with UUID_TO_BIN(x, 1), whose second argument reorders the time fields so the value sorts.
SQLiteNoneGenerate it in your application and store 16 bytes as a BLOB.
MongoDBUUID() in the shellThe default _id is an ObjectId, not a UUID. It is 12 bytes and already time-ordered, so on MongoDB the version 7 argument is usually moot.

When a UUID is the wrong choice

Three cases, and all three come up more often than the version question does.

When a human has to read it out. Thirty-six characters is unusable on the phone, in a support ticket or on a printed label. Keep the UUID as the internal key and give the object a short public reference beside it.

When it needs to be unguessable. A UUID is an identifier, not a token. Use 256 bits of randomness from your platform's secure generator and treat it as a secret, including not logging it.

When a plain integer would do. A bigint is 8 bytes rather than 16, it sorts, it is readable, and every database has a fast path for it. UUIDs earn their keys when identifiers must be created in more than one place at once, offline, or before the row reaches a server. If none of that is true, the integer is the simpler answer.

Every measurement on this page was taken on 2 September 2026 and can be repeated. The timings and the ordering test run against the generator itself, in your own browser, and the node field result is one paste into the reader at the bottom of that page.

Frequently asked questions

Is version 7 safe to use in production yet?

Yes. It was standardised in RFC 9562 in May 2024, which replaced the old RFC 4122, and the major libraries shipped it before that. PostgreSQL added a built-in uuidv7() in version 18. If your language’s UUID library is current, it has version 7. The only real caution is that a version 7 identifier exposes the moment it was created, which is a property, not a bug, and one you have to decide you can live with.

Does a UUID primary key really slow a database down?

A random one can, and the reason is the index rather than the key. A B-tree index keeps its entries in order, so a random value lands in a random page: at scale that means reading a page from disk for nearly every insert, and splitting pages that were already full. A sequential value lands at the end of the tree every time, in a page already in memory. That difference is the whole argument for version 7, and it is why "UUIDs are slow" and "random UUIDs are slow" get confused for each other.

Should I store a UUID as text or as binary?

Binary, if your database has a native type. PostgreSQL has uuid at 16 bytes; SQL Server has uniqueidentifier; MySQL does not, so use BINARY(16) with UUID_TO_BIN(). Storing the 36-character text form costs 36 bytes instead of 16 in every row and in every index entry that references it, which on a large table is the difference between an index that fits in memory and one that does not.

Can I sort by a version 4 UUID to get chronological order?

No, and this is the mistake that sends people looking for version 7 in the first place. A version 4 is 122 random bits with nothing time-related in it at all, so sorting a column of them gives you an arbitrary order that looks stable and means nothing. If you need creation order, either keep a timestamp column or use a version that has one in it.

What is the difference between UUID and ULID?

Very little now, and that is the point. ULID appeared in 2016 to solve exactly the problem version 7 solves: a 48-bit millisecond timestamp followed by random bits. The differences are the encoding, Crockford base32 in 26 characters rather than hex in 36, and the standing. Version 7 is in an IETF RFC with support in PostgreSQL, .NET and every major UUID library; ULID is a specification in a GitHub repository. For new work, version 7 is the one with the ecosystem.

Are UUIDs a security risk if they are guessable?

A version 4 is not guessable, and the other versions are not secrets. Never treat any UUID as an authorisation token: the useful property is uniqueness, not unpredictability, and versions 1, 6 and 7 are partly predictable by design because they start with a clock. If a value has to be unguessable, generate 256 bits of randomness and say so, rather than borrowing an identifier format and hoping.

move openesc close