UUIDs and ULIDs are schemes for issuing “globally unique” IDs in distributed systems and databases. They look similar at first glance, yet differ in standardization status, storage efficiency, human readability, and the risk of leaking information. This article compares the three dominant approaches—UUID v4, UUID v7, and ULID—and closes with notes on UUID v1 and UUID v6.

If you want to experiment in the browser, the following tools handle both generation and timestamp extraction entirely client-side:


UUID v4: the purely random variant

UUID v4 dedicates 122 of its 128 bits to randomness, keeping collision probability astronomically low. The trade-off is that the IDs do not sort in chronological order, so B-tree indexes experience frequent page splits and poor insertion locality. Use v4 for session IDs, one-time tokens, and other cases where “pure randomness” is desirable.


UUID v7: the chronological evolution

Standardized in RFC 9562 (2024), UUID v7 stores the UNIX epoch timestamp in milliseconds in the top 48 bits and fills the remainder with randomness. Lexicographic order (string comparison) becomes identical to generation order (time order), so using v7 as a primary key preserves index locality. It also fits existing uuid types or BINARY(16) columns without modification.

  • Randomness budget: apart from the version and variant bits, v7 keeps roughly 74 bits of randomness. Some implementations perform monotonic adjustments to avoid collisions within the same millisecond; even so, the entropy is more than sufficient.
  • Information exposure: the ID reveals generation time with millisecond precision. Weak randomness combined with high issuance rates can make neighboring IDs easier to guess. Internal systems can generally accept this, but public endpoints that must hide ordering should evaluate the risk.

ULID: human-friendly IDs

ULID, proposed in 2016, has become a de facto standard. It uses Crockford Base32 (26 characters), omitting easily confused characters such as O/I/L/1, making it well-suited for URLs and copy-paste workflows. Like v7, the top 48 bits store the timestamp (ms), so lexicographic order matches chronological order.

  • Sorting guarantee: ULIDs always compare in chronological order when treated as Base32 strings (monotonic generation is widely implemented to avoid collisions).
  • Storage format: storing them as TEXT(26) preserves readability, but binary storage in databases requires Base32↔16-byte conversion. Most RDBMS UUID types cannot store ULIDs directly.
  • Information exposure: just like v7, ULIDs reveal timestamps (ms). If you do not want API consumers to infer issuance order, consider alternatives.

Comparison table (practical view)

Aspect UUID v4 UUID v7 ULID
Standardization RFC 4122 RFC 9562 (2024) De facto, not standardized
Bit layout 128 bits with 122 random 48-bit UNIX ms + remaining random (monotonic optional) 48-bit UNIX ms + 80-bit random
Random entropy ≈122 bits ≈74 bits (after version/variant) 80 bits
String form 36 chars (hex with hyphens) 36 chars (hex with hyphens) 26 chars (Crockford Base32)
Natural ordering (text) ✗ (random) ✓ (lexicographic = chronological) ✓ (lexicographic = chronological)
Natural ordering (binary) ✓ (BINARY(16) compares in ascending time order) ✓ (when the 6-byte timestamp is big-endian before the random tail)
Database fit uuid / BINARY(16) are ideal uuid / BINARY(16) are ideal TEXT(26) keeps readability; BINARY(16) requires conversion and many UUID types reject it
Index locality Poor (random insert hot spots) Good (append-friendly, though hot spots can appear) Good (append-friendly, same caution)
Equality check cost Low (16-byte compare) Low (16-byte compare) TEXT comparisons cost more; BINARY is low
Encoding overhead Hex↔16 bytes (cheap) Hex↔16 bytes (cheap) Base32↔16 bytes (heavier)
Human readability / URL suitability Low Low High
Information exposure None Timestamp (ms) Timestamp (ms)
Typical uses Session IDs, one-time keys Database primary keys, event IDs, log ordering Public IDs and logs where readability helps

Hot-spot caution for v7/ULID: if all inserts target a single shard or leader, append-heavy workloads can still produce hot B-tree pages. Techniques such as prefix shuffling or combining the ID with a shard key help spread the load.


Database implementation notes (PostgreSQL / MySQL / SQLite)

  • PostgreSQL
    • v4/v7: the uuid type is optimal. Import v7 as text, cast to uuid, and you get natural ordering.
    • ULID: use char(26) / text or bytea(16) after Base32 conversion.
  • MySQL / InnoDB
    • BINARY(16) is compact and fast for v4/v7. v7 remains time-ordered in big-endian form. ULID fits CHAR(26) or BINARY(16) with conversion.
  • SQLite
    • There is no native UUID type. Use BLOB(16) or TEXT. Indexed BLOBs work well enough.

Security considerations

  • Guess resistance: v7/ULID share the same timestamp bits. If you issue many IDs within the same millisecond and the random generator is weak, nearby IDs become easier to predict. Use cryptographically secure PRNGs and keep monotonic adjustments unbiased.
  • Metadata leakage: IDs expose the generation time and rough issuance volume. For public APIs, consider separating internal IDs from the identifiers you expose externally.

Summary: choosing the right option

  • UUID v4: pure randomness. No inherent ordering. Theoretical collisions are negligible. Best suited for session IDs, CSRF tokens, and similar use cases.
  • UUID v7: standardized ordered UUID. Ideal for internal systems and database primary keys. Monotonic generation makes collisions practically impossible.
  • ULID: comparatively human-friendly. Great for URLs and logs when readability matters, but often less convenient than v7 for internal primary keys. Monotonic generation keeps collisions at bay.

Appendix: UUID v1 and v6 (briefly)

  • UUID v1 (time-based + MAC address) Uses a 60-bit timestamp (100 ns resolution) plus a node identifier (often the MAC address). It offers strong chronological order but risks exposing host information via the MAC address. Implementations must also handle clock rollback carefully. Modern systems often avoid v1 for privacy reasons.

  • UUID v6 (reordered v1) Rearranges v1’s timestamp into big-endian order for better sorting. Historically viewed as a “sortable v1”, but standardization converged on v7 instead. New designs should prefer v7 over v6.


Implementation checklist

  • Use a cryptographically secure PRNG for randomness.
  • When using monotonic generation for v7/ULID, avoid introducing biased randomness while preventing same-millisecond collisions.
  • Favor uuid / BINARY(16) columns to dodge collation costs on string columns.
  • Decide on an external ID policy (do you allow exposing issuance order?). Separate internal and external IDs when necessary.

Bottom line: with today’s tooling, UUID v7 is the default choice for internal primary keys, while ULID is compelling when human readability or legacy constraints matter. Reserve v4 or other schemes for the rare scenarios where timestamp exposure is unacceptable.