All articles
169 articles · updated weekly See our Tools
All articles
Tutorials

What Is a UUID? Format, Versions, and When to Use It

A UUID is a 128-bit identifier that requires no central coordination. Learn the format, all 8 versions (v1 to v8), the NIL UUID, and when each version makes sense.

What Is a UUID? Format, Versions, and When to Use It
COVER · Tutorials

At some point someone asked you "what ID do you use in your tables?" and the answer got complicated. Auto-increment is simple but leaks information about volume. UUIDs are opaque but seem excessive to anyone who has never needed to coordinate inserts across multiple services without a central database in the way. The truth is that UUID solves a specific problem elegantly — and knowing exactly what it is, how it is structured, and which versions exist makes a difference when you need to make that decision.

This post covers the format and the versions. For the direct comparison between v4 and v7 — which is where most production questions come up — the post UUID v4 vs UUID v7: What's the Difference? goes into the performance and database details.

What a UUID is

UUID stands for Universally Unique Identifier. It is a 128-bit number represented as 32 hexadecimal digits divided into 5 groups by hyphens: 8-4-4-4-12. The result looks like this:

550e8400-e29b-41d4-a716-446655440000

What makes a UUID interesting is not the size — it is the uniqueness property without central coordination. You can generate a UUID on any machine, offline, without consulting any service, and the probability of collision with any other UUID ever generated is astronomically small. For v4 with 122 bits of randomness, you would need to generate approximately 2.7 × 10¹⁸ UUIDs to have a 50% chance of collision. That will never happen in practice.

That is the problem UUID solves: creating unique identifiers in distributed systems where multiple nodes need to generate IDs without communicating.

The anatomy of the 128 bits

The structure of a UUID is not just visual formatting. The 128 bits have specific fields defined by RFC 4122 (updated by RFC 9562 in 2024):

550e8400 - e29b - 41d4 - a716 - 446655440000
xxxxxxxx   xxxx   Mxxx   Nxxx   xxxxxxxxxxxx

The digit M (bits 12–15 of the third group) indicates the version. In the example above, 4 means version 4. The digit N (bits 6–7 of the fourth group) indicates the variant — a in hexadecimal is 1010 in binary, and the two most significant bits 10 indicate the RFC 4122 variant (the standard one).

This structure is why a parser can identify the version of a UUID just by looking at it.

UUID versions

The specification defines 8 versions, each with a different generation strategy:

Version 1 — Timestamp + MAC address Combines the current timestamp (in 100-nanosecond intervals since October 15, 1582) with the MAC address of the network interface. Technically sortable, but problematic for privacy: the server's MAC address is embedded in every generated ID. Rarely used in new systems.

Version 2 — DCE Security A variant of v1 that replaces part of the timestamp with a POSIX identifier (UID or GID). Almost never seen in practice outside legacy DCE contexts.

Version 3 — Namespace + MD5 Deterministic: given the same namespace and the same name, it always generates the same UUID. Uses MD5 as the hash function. Useful for creating stable IDs for resources identified by name, like URLs or email addresses. MD5 is no longer considered secure for cryptographic applications, so v5 is preferable for new cases.

Version 4 — Random 122 random bits, with the remaining 6 bits defining version and variant. This is the most widely used version today — practically the default in every library and language. No hardware dependency or external state; pure randomness from a CSPRNG.

Version 5 — Namespace + SHA-1 Same as v3 but uses SHA-1 instead of MD5. More secure than v3 and equally deterministic. Use v5 whenever you need UUIDs derived from a namespace and name.

Version 6 — Reordered timestamp A reordering of the v1 fields to make the UUID lexicographically sortable by time, maintaining compatibility with the Gregorian timestamp structure of v1. Introduced by RFC 9562. Rare in practice — v7 replaced its use case with a cleaner approach.

Version 7 — Unix timestamp + random 48 bits of Unix timestamp in milliseconds at the start of the UUID, followed by 74 random bits. Chronologically sortable, generated simply, with no MAC address dependency. The modern successor to v1 for primary keys. Defined by RFC 9562 in 2024.

Version 8 — Custom A format for custom implementations that do not fit the previous versions. The RFC only defines that the version and variant bits must be respected; the rest is up to the application. Used by projects that need specific properties not covered by standard versions.

The NIL UUID and the Max UUID

Two special values are worth mentioning:

The NIL UUID is 00000000-0000-0000-0000-000000000000 — all bits zero. It is the equivalent of a null or sentinel value. Used in code to represent "absence of a UUID" explicitly, rather than using null.

The Max UUID is ffffffff-ffff-ffff-ffff-ffffffffffff — all bits one. Introduced by RFC 9562 as the opposite of NIL, useful in range comparisons.

How UUIDs are used in practice

Primary key in a database: the most common use. UUID as a PK eliminates the need for a central sequencer, allows different services to generate IDs independently, and data merges between systems happen without ID conflicts. The cost is larger indexes and, depending on the version, B-tree fragmentation (v4 is random and fragments; v7 is ordered and does not — see UUID v4 vs UUID v7).

Session ID and authentication token: UUID v4 as a temporary token. The length and randomness are sufficient for most cases, though high-security session tokens sometimes use more bits or different formats (like PASETO).

Idempotency key in APIs: when a client makes a payment request and does not know if it arrived, resending with the same UUID ensures the server processes it only once. The server stores the UUID and returns the cached result for duplicate requests.

Event IDs in event sourcing: each event receives an immutable UUID that serves as a permanent identifier in the log. With v7, events are naturally ordered by creation time.

IDs in distributed systems and microservices: two independent services can generate IDs without collision risk. Without UUID, you need a centralized generator (which becomes a single point of failure) or ID range partitioning (which is manual and error-prone).

Native support by platform

// JavaScript (browser and Node.js 14.17+)
crypto.randomUUID(); // "b7a1c3d4-e5f6-4789-abcd-ef0123456789"
-- PostgreSQL
SELECT gen_random_uuid(); -- generates v4

-- MySQL 8.0+
SELECT UUID(); -- generates v1 (note: this is v1, not v4)
# Python
import uuid
uuid.uuid4()  # version 4
uuid.uuid5(uuid.NAMESPACE_URL, 'https://example.com')  # version 5
// Go — standard library has no UUID; use github.com/google/uuid
id := uuid.New() // v4

To generate v4 UUIDs quickly in the browser without installing anything, the UUID Generator covers v4 and v7 with direct clipboard copy.

UUID vs ULID vs NanoID

Three formats frequently compared:

ULID (Universally Unique Lexicographically Sortable Identifier): 128 bits like UUID v7, but represented in Crockford Base32 (01H4P3KGQ1ABCDE...). More compact textually, sortable. No official standard adoption — it is an independent spec.

NanoID: a random string of configurable length (default 21 characters), URL-safe. Shorter than UUID for display, but without a version structure or timestamp. Good for IDs in URLs, poor for database PKs where you want sortability.

UUID: international standard (RFC 9562), native support in databases, languages, and frameworks. Less visually compact, but the most interoperable. For most database and API use cases, UUID is the obvious choice.

Frequently asked questions

Can UUID v4 collide with another UUID v4?

Theoretically yes, in practice no. With 122 bits of randomness, the probability of generating two identical UUIDs is so small that, for any reasonable system scale, it is effectively zero. To have a 50% chance of collision, you would need to generate 2.7 × 10¹⁸ UUIDs. For context: the entire world generates something around 10¹² UUIDs per year.

Is UUID v1 still safe to use?

For new systems, it is not worth it. The main problem with v1 is not technical — it is that it embeds the server's MAC address in the ID, which exposes information about the infrastructure. Additionally, the v1 timestamp uses a non-standard scale (100ns since 1582). For cases that need time-sortable UUIDs, v7 is the correct answer.

Should I store UUID as TEXT or BINARY(16) in the database?

It depends on the database and volume. In PostgreSQL, the native UUID type (16 bytes) is more efficient than TEXT (37 bytes with hyphens) for indexing and storage. In MySQL, there is no native UUID type — BINARY(16) is more efficient than VARCHAR(36) for large tables, but requires manual conversion. For most projects with PostgreSQL, use the native UUID type and do not worry about it.

Does UUID v7 already have support in frameworks and ORMs?

Growing rapidly. Hibernate supports it since 6.2; TypeORM has experimental support; Prisma still depends on extensions for server-side generation. At the application level, libraries like uuidv7 (npm) and github.com/gofrs/uuid (Go) already support it. PostgreSQL 17 added native uuidv7(). The ecosystem is still migrating, but v7 is the direction.

What UUID actually solves

UUID is a solution to a coordination problem. When you have a single database with a single sequencer, auto-increment works fine. When you have multiple services, multiple database replicas, or need to generate IDs offline before persisting, UUID eliminates the dependency on a central point.

The choice of version matters more than it seems: v4 is simple and ubiquitous, but fragments indexes at scale. v7 is ordered and solves that problem at the cost of an additional library today (and no cost at all tomorrow, when native support is ubiquitous). For new projects with tables that will grow, v7 is the right decision.

RD
Author
Rafael Duarte
Backend developer with a background in fintech and B2B SaaS — worked on teams that scaled APIs from zero to millions of requests. Carries enough production scars to hold strong opinions on tools, patterns and architecture decisions. Not an academic: he read the UUID RFC when he had to choose between v4 and v7 for a write-heavy table.
View profile