UUID generate.
Cryptographically random v4, time-sortable v7 (RFC 9562, May 2024), or the nil UUID. Generated locally with crypto.getRandomValues — never round-trips a server.
47cd25c7-2db6-4a0c-9f30-26a77197f4c3502fefb6-35d3-4dbf-939f-459307d0ea4c77ec957b-efa6-400f-aa13-f137e3dbc0b9130afe2e-4dfd-4783-b04a-c29097f8ede776bc9d03-32fc-4459-a736-09497fe6e2a8
Identifiers without a central allocator.
Before identifiers became universally unique, they were locally clever. Early systems leaned on sequential integers handed out by a single source of truth: a database sequence, an Oracle SEQUENCE, a file with a counter and a lockfile. This worked when one machine owned the keyspace. The moment two machines needed to mint IDs without talking to each other, the model collapsed. Teams worked around it with prefixed strings ("NYC-00041", "SF-00041"), hierarchical IDs that encoded a region and a node and a local counter, or hash-based schemes that hoped for the best. Every one of these required either coordination or convention, and conventions drift the moment a system grows past three engineers.
The breakthrough came out of Apollo Computer's Network Computing System in the 1980s. Apollo needed to identify objects across a distributed network of workstations where no central allocator existed and no two nodes could be trusted to agree on a counter in real time. The solution was to encode enough entropy and enough machine-local information into a single 128-bit value that two nodes generating IDs independently would, with overwhelming probability, never collide. That design moved into the Open Software Foundation's Distributed Computing Environment (OSF DCE 1.1), where UUIDs became the identifier substrate for RPC endpoints, interface definitions, and security principals.
RFC 4122 in July 2005 crystallised what the industry had already been doing informally, defining versions 1 through 5 and the canonical 8-4-4-4-12 hex representation. Two decades later, RFC 9562 (May 2024) obsoleted 4122 and added versions 6, 7, and 8 to address the realities of modern systems: clustered databases, sortable inserts, and the death of the assumption that anyone wanted their MAC address in a primary key.
A v4 UUID has 122 bits of randomness, which gives you a 50% collision probability after roughly 2^61 IDs — a number you will not reach. UUIDs are collision-resistant, not collision-impossible, and the distinction matters when you start treating them as cryptographic tokens rather than identifiers.
Eight versions, three you'll actually use.
Version 1 encodes a 60-bit timestamp (100-nanosecond intervals since 15 October 1582, the Gregorian reform), a 14-bit clock sequence, and a 48-bit node identifier traditionally sourced from the network card's MAC address. It is strictly sortable by generation time and effectively impossible to collide on a single machine. It also leaks the MAC address of whatever generated it, which is why early Microsoft Word documents could be traced back to specific machines and why the Melissa virus author was identified in 1999. Some shops still use v1 internally because it sorts correctly in B-tree indexes and the MAC leak is irrelevant inside a private network. Version 2 is the DCE Security variant, which embeds POSIX UID/GID values in place of part of the timestamp. It is rare to the point of being nearly extinct in modern stacks; most libraries do not implement it.
Versions 3 and 5 are namespaced and deterministic: feed in a namespace UUID and a name string, and you get the same UUID every time. Version 3 uses MD5, version 5 uses SHA-1. They are useful when you need stable identifiers for things that already have natural names — DNS records, URLs, OIDs — without storing a mapping table. The deterministic property is the feature, but it is also the leak: anyone who knows the namespace and name can reproduce the UUID. Version 4 is the workhorse: 122 bits of CSPRNG output, six bits reserved for version and variant, no information disclosure, no time correlation. It is what most systems mean when they say "give me a UUID."
RFC 9562 added three new versions in May 2024. Version 6 is essentially v1 with the timestamp bytes reordered so the most significant bits come first, making it sortable as a raw byte sequence. It exists for shops that have v1 infrastructure and want sortability without changing their generation pipeline. Version 7 is the new default — covered in detail below. Version 8 is explicitly a custom-format escape hatch: as long as you set the version and variant bits correctly, the remaining 122 bits are yours. It is the right choice when you need to embed a tenant ID, a shard key, or a hash prefix into the UUID itself without inventing a new identifier format.
Why v7 wins for new systems.
Version 7 packs a 48-bit Unix millisecond timestamp into the high bits, followed by 12 bits of randomness in the rand_a field, the version and variant nibbles, and 62 bits of randomness in rand_b. The result is an identifier that sorts by creation time at millisecond resolution and falls back to randomness for ordering within a millisecond. You get k-sortability for free: a query like ORDER BY id LIMIT 50 returns the most recently inserted rows without consulting a separate created_at index. For event logs, audit trails, and append-mostly tables, this collapses two indexes into one.
The database performance argument is more compelling than the API ergonomics. When v4 UUIDs are used as primary keys in a B-tree index — which is how Postgres, MySQL InnoDB, and SQL Server all store clustered or primary indexes — every insert lands at a random leaf page. Pages get split, the buffer cache thrashes, and write throughput collapses on tables larger than RAM. This is the v4 hot-page problem, and it is the single most common reason teams migrate off random UUIDs at scale. Version 7 inserts always land at the rightmost leaf, the same access pattern as an auto-increment BIGINT, which keeps page splits localised and the working set small.
Tooling caught up in 2024 and 2025. Postgres 18 shipped a native uuidv7() function, removing the need for the uuid-ossp extension or a userspace generator. Java 17 already exposed the primitive through java.util.UUID, with libraries like jbock-java/uuid-creator and com.github.f4b6a3 providing v7-aware factories. Python's standard library added uuid.uuid7() in 3.13, and the broader ecosystem (uuid7 on PyPI, uuid6 backport) covers older runtimes. Go's google/uuid added NewV7 in v1.6.0. Node 20 exposes crypto.randomUUID for v4, and v7 ships in the uuid npm package as of v10.
The only legitimate reason to default to v4 over v7 in 2026 is if leaking creation time is a problem for your threat model — for instance, anonymous user IDs where the creation timestamp could deanonymise account-creation patterns. For everything else, v7 is strictly better.
UUIDs aren't the only 128-bit IDs.
UUIDs are not the only 128-bit identifiers in town, and several alternatives predate v7 by years. ULID, designed by Alizain Feerasta in 2016, encodes a 48-bit timestamp and 80 bits of randomness, then renders the result in Crockford base32 as a 26-character string. It is lexicographically sortable as text — no byte-order surprises — and avoids ambiguous characters like I, L, O, and U. Twitter's Snowflake is older and tighter: a 64-bit integer composed of a 41-bit millisecond timestamp, a 10-bit machine ID, and a 12-bit per-millisecond sequence. The 8-byte size is its selling point against UUIDs' 16, but it requires coordinated machine-ID assignment, which is a non-trivial operational burden. Discord uses a Snowflake variant with a different epoch (1 January 2015) and the same general layout.
KSUID, from Segment, is 160 bits: a 32-bit timestamp and 128 bits of randomness, rendered as a 27-character base62 string. It trades a few bytes for more entropy and a longer usable timestamp range than UUIDv7. NanoID is the minimalist option — a configurable-length, URL-safe random string with no timestamp at all, popular in JavaScript ecosystems where short IDs matter for URLs. Cuid2, the successor to the original Cuid, uses a hash construction over multiple entropy sources and is designed to resist the kind of fingerprinting attacks that affect timestamp-based IDs.
| Format | Size | Sortable | Coordination | Best for |
|---|---|---|---|---|
| UUIDv4 | 128 bit | no | none | general, no time leak |
| UUIDv7 | 128 bit | k-sortable | none | new systems, DB keys |
| UUIDv1 | 128 bit | yes | none | legacy, internal |
| ULID | 128 bit | lex-sortable | none | human-visible, logs |
| Snowflake | 64 bit | yes | machine ID | high-volume, compact |
| KSUID | 160 bit | lex-sortable | none | long timestamp range |
| NanoID | variable | no | none | short URLs, tokens |
Pick Snowflake when bytes matter and you can afford the operational coordination — you save 50% on every index. Pick ULID when humans will see and copy the ID. Pick UUIDv7 when you want the broadest interop and the default behaviour to be correct.
Storage cost is real.
Postgres has had a native uuid type since 8.3, stored as 16 raw bytes with hex rendering at the wire boundary. Indexes sort by byte order, which means v1, v6, and v7 all sort correctly without conversion. MySQL is the cautionary tale: the default storage for UUID is CHAR(36), which means every primary key consumes 36 bytes plus index overhead, and string comparisons drive every B-tree lookup. The standard remediation is BINARY(16) with UUID_TO_BIN() and BIN_TO_UUID(), which gets you back to 16-byte storage and integer-speed comparisons. MySQL 8.0 added the swap_flag argument to UUID_TO_BIN that reorders v1 timestamp bytes for better index locality.
MongoDB stores UUIDs as BSON binary subtype 4, which is 16 bytes plus a one-byte type tag. SQLite has no native UUID type and stores them as TEXT (37 bytes including the null terminator) or BLOB (16 bytes); the BLOB form is the right answer for any non-trivial table. The index-size implication is real: a 16-byte UUID primary key produces a clustered index roughly twice the size of an 8-byte BIGINT equivalent, and that size multiplies through every secondary index that includes the primary key as a back-pointer. On a billion-row table, that is gigabytes of extra RAM pressure.
This is why some shops run a hybrid: an internal auto-increment BIGINT primary key for join performance and storage efficiency, plus a UUID column with a unique index for external-facing URLs and API responses. You pay the cost of the UUID index once instead of cascading it through every foreign key.
Where UUIDs go wrong.
The collision math for v4 is reassuring but worth understanding. With 122 bits of entropy, you hit a 50% collision probability around 2^61 IDs — that is roughly 2.3 quintillion. Generating a billion v4 UUIDs per second, you would need about 73 years to reach that threshold. The practical concern is not the birthday bound; it is the random source. Math.random() in JavaScript is not cryptographically secure, and neither is java.util.Random or Python's random module. UUIDs generated from these sources can be predicted from a few observed outputs. The correct primitives are crypto.getRandomValues or crypto.randomUUID in browsers and Node, java.security.SecureRandom in the JVM, and secrets or os.urandom in Python.
Different versions leak different things. Version 1 leaks the MAC address and the generation timestamp. Versions 3 and 5 leak the namespace and name pair to anyone who knows the inputs — they are deterministic by design. Version 7 leaks the creation time at millisecond resolution, which is acceptable for most uses but disqualifies it for anonymous identifiers where timing correlation matters. Version 4 leaks nothing, which is its enduring appeal for tokens and session IDs.
Sortability is where teams get burned in production. Version 4 is not sortable in any meaningful sense. Version 7 is k-sortable: ordered at millisecond granularity, randomised within a millisecond. Versions 1 and 6 are strictly sortable. The byte-order trap appears at the storage boundary: a v7 UUID round-tripped through Postgres preserves its byte order and sorts correctly in an index. The same UUID stored as a VARCHAR in MySQL sorts as text, which gives you the right answer for the hex representation by coincidence — until you start mixing case or trimming hyphens, at which point the ordering silently breaks. Always store UUIDs as their native binary type when the database supports one.
Pick by where the ID will live.
A UUID has two jobs: be unique without coordination, and not leak. v4 is the default for anything user-facing or generated from many independent processes — there's no time signal, no MAC address, no sequence to reverse-engineer. v7 is the better default for database primary keys because the time prefix turns inserts into appends. v1 (timestamp + MAC) and v6 (sortable v1) exist but are largely superseded by v7. The nil UUID — all zeros — is reserved as the "absent" sentinel; never use it as a real identifier.
The new UUID RFC obsoletes RFC 4122 (2005) and adds v6, v7, and v8. v7 is the one that has been quietly waiting for industry adoption — Postgres added native uuidv7() in version 18, and most language runtimes shipped helpers in 2024–2025.