UUID vs ULID vs NanoID: The Definitive Comparison
A thorough comparison of the most popular unique identifier formats, with practical guidance on when to use each one.
The Problem: Choosing a Unique Identifier
Nearly every software system needs unique identifiers. Whether you are building a REST API, designing a database schema, creating user sessions, or generating correlation IDs for distributed tracing, you need a way to create identifiers that are unique, collision-resistant, and suitable for your specific use case.
UUIDs have been the default choice for decades, but the landscape has evolved. Several modern alternatives now exist, each with distinct trade-offs. This guide compares the five most popular formats: UUID v4, UUID v7, ULID, NanoID, and CUID2.
UUID v4: The Universal Standard
UUID v4 generates 122 bits of random data packed into the standard 128-bit UUID format. The canonical representation is a 36-character string with hyphens: 550e8400-e29b-41d4-a716-446655440000. UUID v4 is implemented in the standard library of virtually every programming language and is the most widely recognized identifier format in the industry.
UUID v4 strengths include universal recognition, massive collision resistance (122 bits of entropy), and zero coordination requirements. Its weaknesses are a relatively large string size (36 characters), poor URL-friendliness due to hyphens, and random distribution that causes B-tree fragmentation in databases.
UUID v7: The Modern UUID
UUID v7, standardized in RFC 9562 (May 2024), places a 48-bit millisecond-precision Unix timestamp in the most significant bits, followed by random data. The result is a UUID that maintains the standard 128-bit, 36-character format but sorts chronologically when compared as strings or bytes.
UUID v7 was designed specifically to solve the database performance problem of random UUIDs. Because v7 values increase monotonically over time, they produce sequential B-tree inserts, eliminating the page-split overhead that makes v4 slow as a primary key. UUID v7 is backward-compatible with existing UUID infrastructure: any system that accepts a UUID string will accept a v7 UUID. For a deeper look at UUID versions, see our UUID Versions Explained guide.
ULID: Universally Unique Lexicographically Sortable Identifier
ULID is a 128-bit identifier that encodes a 48-bit Unix timestamp in milliseconds and 80 bits of randomness. Its defining feature is the Crockford Base32 encoding, which produces a 26-character string like 01ARZ3NDEKTSV4RRFFQ69G5FAV. This encoding is case-insensitive, avoids ambiguous characters (like I, L, O, and U), and preserves lexicographic sort order.
ULID was created before UUID v7 existed, and it addressed the same problem: sortable, timestamp-embedded identifiers. ULIDs are shorter than UUIDs (26 vs 36 characters), naturally sort by creation time, and are URL-safe without encoding. However, ULID is not an official standard (no RFC), and ULID libraries are not as universally available as UUID implementations.
One important property of ULID is monotonicity within the same millisecond. If multiple ULIDs are generated in the same millisecond, the random portion is incremented rather than regenerated, guaranteeing strict ordering even at high throughput.
NanoID: Compact and Customizable
NanoID takes a fundamentally different approach: instead of a fixed 128-bit format, it generates a URL-safe string of configurable length using a customizable alphabet. The default configuration produces a 21-character string using the characters A-Za-z0-9_-, yielding approximately 126 bits of entropy. A typical NanoID looks like V1StGXR8_Z5jdHi6B-myT.
NanoID is the smallest of the formats covered here (21 characters default), is URL-safe by design, and allows you to customize both the length and alphabet. It is especially popular in frontend applications, short URLs, and any context where compact identifiers matter. The trade-off is that NanoID has no timestamp component, no built-in monotonicity, and is not standardized by an RFC.
NanoID uses a cryptographically secure random number generator (crypto.getRandomValues() in browsers, crypto.randomBytes() in Node.js), and the generation algorithm carefully avoids modulo bias, ensuring uniform distribution across the alphabet.
CUID2: Collision-Resistant with Built-In Security
CUID2 is a collision-resistant identifier designed for horizontal scaling and security. It produces a 24-character string that always starts with a letter (important for some HTML and CSS contexts). Internally, CUID2 combines a timestamp, a counter, a machine fingerprint, and random data, then hashes the result with SHA-256. A typical CUID2 looks like clh3amdp10000mj08bz5a3k4u.
The SHA-256 hashing step means that CUID2 does not leak timing information: unlike UUID v7 or ULID, you cannot extract the creation timestamp from a CUID2. This provides better security properties in contexts where timestamps could be used for enumeration attacks. The trade-off is that CUID2 identifiers are not sortable by creation time.
CUID2 is the successor to CUID (v1), which was deprecated due to security concerns. CUID2 addresses these by hashing the output, making it impossible to predict future IDs from observed ones.
Head-to-Head Comparison
| Property | UUID v4 | UUID v7 | ULID | NanoID | CUID2 |
|---|---|---|---|---|---|
| String length | 36 | 36 | 26 | 21 | 24 |
| Bits of entropy | 122 | 74 | 80 | ~126 | ~96 |
| Timestamp embedded | No | Yes (ms) | Yes (ms) | No | Hidden |
| Lexicographic sort | No | Yes | Yes | No | No |
| URL-safe | No (hyphens) | No (hyphens) | Yes | Yes | Yes |
| RFC standard | RFC 9562 | RFC 9562 | No | No | No |
| DB index perf | Poor | Excellent | Excellent | Poor | Moderate |
Database Performance: Why Ordering Matters
The most impactful difference between these formats is whether they are time-ordered. Databases store indexes in B-tree structures (or variants like B+ trees). When you insert a new row with a random primary key (UUID v4 or NanoID), the database must find the correct position in the tree, which may require splitting existing pages to make room. Over time, this leads to fragmentation, wasted space, and degraded read performance.
Time-ordered identifiers (UUID v7, ULID) always insert at the end of the B-tree, which is the optimal case for write performance. In benchmarks, UUID v7 can achieve 2 to 5 times faster insert throughput compared to UUID v4 in PostgreSQL and MySQL, with the difference growing as the table size increases. For a detailed discussion of UUIDs in databases, see our guide on UUID as Database Primary Key.
When to Use Each Format
Here is a practical decision framework based on common scenarios:
- REST API resource IDs (general purpose): UUID v4. Universal compatibility, massive ecosystem support, and every developer recognizes the format.
- Database primary keys: UUID v7. Timestamp ordering gives you the best of both worlds: global uniqueness and excellent index performance. This is the recommended choice for new projects.
- Short URLs or frontend IDs: NanoID. At 21 characters, it is the most compact option while still providing strong collision resistance. The URL-safe alphabet means no encoding needed.
- Distributed event logs or time-series data: ULID. The Crockford Base32 encoding and strict millisecond monotonicity make ULIDs excellent for event streams where ordering and compactness both matter.
- Security-sensitive contexts (no timing leaks): CUID2 or UUID v4. If an attacker observing IDs should not be able to infer creation order or rate, use a format that does not embed readable timestamps.
- Maximum interoperability with existing systems: UUID v4 or v7. Both use the standard UUID format recognized by PostgreSQL's
uuidtype, RFC-compliant validators, and every major framework.
Migration Considerations
If you are currently using UUID v4 and want to switch to UUID v7 for better database performance, the migration is straightforward because both use the same 128-bit UUID format. Existing UUID columns, indexes, and application code will work with v7 values without modification. The only change is on the generation side: use a v7 generator instead of v4.
Migrating from UUID to a different format (ULID, NanoID, CUID2) requires schema changes, as the string representation is different. This is a more significant migration that typically requires a phased approach with backward compatibility during the transition.
Try It Yourself
Our UUID Generator includes an Alternatives tab where you can generate UUID v4, UUID v7, ULID, NanoID, and CUID2 side by side and compare their properties, including length, encoding, and embedded timestamps. Use the Collision tab to explore how the entropy differences affect collision probability at various generation rates.
Further Reading
- ULID specification
The Universally Unique Lexicographically Sortable Identifier specification.
- nanoid documentation
A tiny, secure, URL-friendly unique string ID generator for JavaScript.
- RFC 9562 — UUIDs
The latest UUID specification including the new time-sortable UUIDv7.
- Cuid2 documentation
Collision-resistant IDs optimized for horizontal scaling and performance.