The version landscape in two minutes
| Version | Source of uniqueness | Sortable | Notes |
|---|---|---|---|
| v1 | Timestamp + MAC address | Partly | Leaks machine identity; legacy |
| v4 | 122 random bits | No | The general-purpose default |
| v5 | SHA-1 of namespace + name | No | Deterministic — same input, same UUID |
| v7 | Timestamp + random | Yes | Best for DB keys (RFC 9562) |
v5 deserves more fame: when you need the same identifier every time for the same input — deduplicating imported records, stable IDs derived from URLs — a name-based UUID does it without coordination or storage.
Collision math, concretely
A v4 UUID has 122 random bits. Generating one billion UUIDs per second continuously, you’d expect roughly 85 years before a 50% chance of a single collision. The practical engineering consequence: generate freely on clients, in parallel, across services, without coordination — and treat any “duplicate UUID” bug as what it actually is, a code path reusing a value or a faulty random source, never genuine bad luck.
Storage and indexing done right
Store UUIDs in native types, not strings: PostgreSQL’s uuid type and MySQL’s BINARY(16) use 16 bytes, while a VARCHAR(36) burns 37+ bytes and compares slower. With random v4 keys on large, write-heavy tables, expect index page splits and cache misses — the problem v7’s time-ordering exists to solve. If you’re stuck on v4 for compatibility, MySQL 8’s UUID_TO_BIN(uuid, 1) swaps the time-ish bits to fake locality for v1; for v4 there is no such trick, so consider v7 before scaling pain arrives.
UUIDs vs the short-ID alternatives
NanoID and ULID solve adjacent problems: NanoID gives URL-friendly 21-character IDs with comparable entropy, ULID gives sortable 26-character Crockford-base32 IDs (essentially v7’s idea with a different alphabet). Choose UUID when you want the universally understood standard with native database and language support everywhere; choose the alternatives when ID aesthetics in URLs matter more than ecosystem ubiquity.
v7 or ULID? The decision most generators don’t let you make
This is the one comparison worth having, and you can only have it if a tool emits both — so here it is with the numbers, since this generator produces either.
Both are 128 bits. Both put a 48-bit Unix-millisecond timestamp at the front, which is what makes them sort chronologically as plain strings. Everything else differs:
| UUIDv7 | ULID | |
|---|---|---|
| Spec | RFC 9562 (2024) | Community spec, no RFC |
| Canonical string | 36 chars, hex + 4 hyphens | 26 chars, Crockford Base32 |
| Bits after the timestamp | 74 random (split by version/variant fields) | 80 random |
| Alphabet | 0-9a-f | 0-9A-Z minus I, L, O, U |
| Same-millisecond ordering | Optional — RFC 9562 describes methods, implementations vary | Required — increment the random field |
| Native database type | Yes: Postgres uuid, MySQL BINARY(16) | No — stored as CHAR(26) or converted |
Three consequences that actually decide it:
- Storage. UUIDv7 drops into a 16-byte
uuidcolumn. ULID has no native type, so you either store 26 characters of text or convert to 16 bytes on the way in and back on the way out — which means every query, migration and ad-hocpsqlsession needs the conversion too. - Ordering within a millisecond. ULID guarantees a monotonic sequence when two IDs are generated in the same millisecond. UUIDv7 leaves that to the implementation. If you are relying on ID order to reconstruct insert order at high write rates, check what your library actually does before assuming it.
- Reading them out loud. Crockford’s alphabet drops
I,L,OandUprecisely so1/Iand0/Ocan’t be confused, and so the encoding can’t spell unfortunate words. If IDs appear in support tickets or get read over a phone, that is not a cosmetic detail.
The short version: UUIDv7 if the ID lives in a database column, because the native type and the ecosystem carry real weight. ULID if the ID lives in a URL or a log line a human has to read or retype.