UUID vs ULID: Which Unique Identifier to Choose
The Short Answer
Use UUID v7 if you want broad ecosystem support with sortability. Use ULID if you want sortability plus a compact, URL-safe string. Both are better than UUID v4 for database-heavy applications.
Format Comparison
| Property | UUID v4 | UUID v7 | ULID |
|---|---|---|---|
| Length | 36 chars | 36 chars | 26 chars |
| Encoding | Hex + hyphens | Hex + hyphens | Crockford's Base32 |
| Timestamp | None | 48-bit ms | 48-bit ms |
| Random bits | 122 | 62 | 80 |
| Sortable | No | Yes | Yes |
| URL-safe | Mostly | Mostly | Yes (no hyphens) |
Visual Comparison
UUID v4: f47ac10b-58cc-4372-a567-0e02b2c3d479 (random)
UUID v7: 0190a6a8-7b1c-7d3e-8f2a-5c9b1d3e4f5a (time-prefixed)
ULID: 01ARZ3NDEKTSV4RRFFQ69G5FAV (time-prefixed, compact)
Notice how v7 and ULID share the same timestamp prefix — both encode the generation time at the start of the identifier.
Sortability: The Real Difference
UUID v4 generates identifiers in random order. When inserted into a B-tree index, each new row lands in a random leaf node, causing:
- Page splits: Database constantly reorganizes index pages
- Cache misses: Random page access defeats the buffer pool
- Write amplification: Fragmented inserts trigger more I/O
UUID v7 and ULID solve this by prefixing a timestamp. New rows append to the right edge of the index, just like auto-increment IDs.
Benchmark Impact
In PostgreSQL with 10M rows:
| Metric | UUID v4 | UUID v7 / ULID |
|---|---|---|
| Insert throughput | ~35K rows/sec | ~55K rows/sec |
| Index size | ~1.4× larger | Baseline |
| Table size | Similar | Similar |
The exact numbers vary by hardware and configuration, but the pattern is consistent: time-ordered identifiers significantly outperform random ones at scale.
UUID v7 vs ULID: Which One?
Since both are time-ordered, the decision comes down to practical differences.
Choose UUID v7 When
- Your database has native UUID type support (PostgreSQL, Cassandra)
- You want RFC-standardized identifiers
- You rely on existing UUID parsing libraries
- You store identifiers as 128-bit binary (16 bytes) rather than text
Choose ULID When
- You want the most compact text representation (26 vs 36 characters)
- Identifiers appear in URLs, logs, or user-facing strings
- You need case-insensitive comparison (Crockford's Base32 avoids ambiguous characters like
0/O) - You want monotonic generation within the same millisecond
Monotonicity
ULID's spec defines monotonic generation: within the same millisecond, each new ULID increments the random portion instead of regenerating it. This guarantees strict ordering even for sub-millisecond inserts.
UUID v7's spec allows monotonic generation but does not mandate it. Implementations vary — check your library.
Migration Considerations
From UUID v4 to UUID v7
- Drop-in replacement in most systems — same 128-bit width, same text format
- Existing v4 IDs remain valid; no data migration needed
- Mixed v4/v7 columns still work; new rows simply benefit from sortability
From UUID to ULID
- Column type may need to change (UUID → text or binary)
- String length changes (36 → 26), affecting foreign keys and indexes
- Existing UUID data must be migrated or stored alongside new ULIDs
- Higher migration cost — plan accordingly
Practical Recommendations
- New project, PostgreSQL: Use UUID v7 — native type support keeps storage at 16 bytes
- New project, MySQL/SQLite: Use ULID — text encoding avoids binary column complexity
- Existing project with UUID v4: Switch new inserts to UUID v7 — zero migration cost
- High-throughput logging: Use ULID — compact strings reduce I/O and storage
- Client-generated IDs: Either works; ULID's shorter string is easier to pass in URLs
Related Guides
Try It Yourself
Generate and compare UUID v4, v7, and ULID formats instantly with our free UUID Generator. See sortability in action — paste a batch of v4 vs v7 IDs and watch the ordering difference.