UUID v4 in practice — collisions, formats and when to use it
What the parts of a UUID mean, why v4 collisions are not a real concern, how to store UUIDs in a database and when a time-ordered UUID v7 is the better choice.
By Felipe YamaokaPublished November 5, 20265 min read
An ID nobody has to hand out
An auto-increment ID is simple until you need IDs before saving, IDs created in several services at once or IDs that do not reveal how many orders you had yesterday. That is where UUIDs come in: 128-bit identifiers that any machine can generate on its own, with no coordination, and that are practically guaranteed to be unique.
This guide explains how to read a UUID, how unique version 4 really is, how to generate and store it correctly and when another version fits better.
Anatomy of a UUID
A UUID is usually written as 32 hexadecimal digits in five groups separated by hyphens, 36 characters in total:
ed65ec92-d01a-4853-adec-7e62a5108f40Two positions are not random, and they let you identify the format at a glance:
- The version is the first digit of the third group. Here it is
4, so this is a UUID version 4. - The variant is the first digit of the fourth group. For standard UUIDs it is always
8,9,aorb.
Everything else in a v4 UUID is random: 122 bits out of 128. Letters can be lowercase or uppercase, and both mean the same value; the specification recommends lowercase on output and accepting either on input. Some systems remove the hyphens and store the 32 digits only.
The current specification is RFC 9562, published in 2024. It replaced the older RFC 4122 and added new versions, including v7.
How likely is a collision?
With 122 random bits there are about 5.3 × 10 to the power of 36 possible UUIDs. The relevant question is not "can two be equal?" but "how many do I need to generate before a repeat becomes likely?", which is the birthday problem.
Some numbers, assuming a good random source:
- Generating 1 million UUIDs, the chance of any repeat is about 1 in 10 to the power of 25.
- Generating 1 billion, about 1 in 10 to the power of 19.
- To reach a 50% chance of a single collision, you would need around 2.7 × 10 to the power of 18 UUIDs. At one billion per second, that takes about 86 years.
In practice, bugs, not math, cause duplicate UUIDs: a weak random generator, a test fixture copied to production or a value reused by mistake. Keep a unique constraint in the database anyway; it costs nothing and catches those bugs.
Generating UUIDs correctly
The randomness must come from a cryptographically secure source. Every modern platform has one built in:
// browsers and Node.js 19+
crypto.randomUUID(); // "ed65ec92-d01a-4853-adec-7e62a5108f40"import uuid
str(uuid.uuid4())-- PostgreSQL 13+
SELECT gen_random_uuid();Avoid hand-written generators based on Math.random(). They are not designed for uniqueness, and some engines have produced repeated sequences. In browsers, crypto.randomUUID() is available only in secure contexts, which means pages served over HTTPS or from localhost.
The UUID Generator on this site uses crypto.randomUUID() directly in your browser. It can generate up to 1,000 at once, in uppercase or without hyphens, which is handy for test data, seeds and fixtures.
Storing UUIDs in a database
How you store a UUID matters more than how you generate it:
- Use a native type when there is one. PostgreSQL's
uuidtype takes 16 bytes. The same value astexttakes 36 bytes plus overhead, and every index on it grows accordingly. - In MySQL, use
BINARY(16)withUUID_TO_BIN()andBIN_TO_UUID()instead ofCHAR(36). - Normalize the format. Store and compare in one case, usually lowercase, so
ED65EC92-...anded65ec92-...are not treated as different keys in systems that compare text.
There is also a performance aspect. Random UUIDs are spread evenly over the whole key space, so every insert lands in a random place in a B-tree index. On large tables this causes more page splits and worse cache use than sequential IDs. For many applications the difference is negligible. For write-heavy tables with hundreds of millions of rows, it is worth considering a time-ordered UUID.
UUID v7: time-ordered UUIDs
Version 7, introduced in RFC 9562, puts a Unix timestamp in milliseconds in the first 48 bits and fills the rest with random data. The result is still unique without coordination, but UUIDs created later sort after earlier ones:
01a1ab94-8600-7c3e-9f5b-3d2a8e6b1c40That gives you the index locality of a sequential ID and lets you sort records by creation time using the key alone. The trade-off is that the creation time is visible to anyone who sees the ID, which may matter for public identifiers. Support is growing: PostgreSQL 18 includes uuidv7(), and libraries exist for most languages.
A reasonable rule: use v4 when the ID is public or when order does not matter, and v7 for primary keys in large tables where insert performance and sorting by time matter.
When not to use a UUID
- When people need to read or type the ID. A short code like an order number is friendlier for support calls.
- As a secret. A v4 UUID is hard to guess, but it is an identifier, not a token. For password reset links or API keys, generate a dedicated random token and store only its hash.
- When a small sequential key is enough. An internal lookup table with a few hundred rows does not need 16-byte keys.
Common mistakes
- Storing UUIDs as
textorvarcharwhen a native type exists. - Mixing uppercase and lowercase in a system that compares strings.
- Generating UUIDs with
Math.random()or another non-cryptographic source. - Dropping the unique constraint because "UUIDs never collide".
- Using a UUID as an access token.
- Exposing v7 UUIDs publicly without considering that they reveal the creation time.
Summary
A UUID v4 is 122 random bits in a standard 36-character format, and with a secure random source collisions are not a practical concern. Generate it with the platform's built-in function, store it in a native 16-byte type and keep a unique constraint. When insert performance or ordering by time matters, consider UUID v7; when the value must stay secret, use a real token instead.
Keep reading
Base64 is not encryption — what it is, when to use it and the URL-safe variant
How Base64 turns bytes into text, why it hides nothing, how to handle accents and emoji correctly and when you need the URL-safe alphabet.
November 5, 20265 min readTool: Base64 Encoder/Decoder
The regex that freezes the browser — catastrophic backtracking explained
Why a short regular expression can take seconds or hang a page, how to recognize nested quantifiers and how to rewrite the pattern so it stays fast.
November 5, 20266 min readTool: Regex Tester