Developer Toolkit IconDeveloper Toolkit
All Articles

UUID vs ULID vs NanoID. Which Unique ID Format Should You Use in 2026 and Why the Wrong Choice Hurts Your Database

A thorough comparison of UUID v4, UUID v7, ULID, and NanoID. Understand the database performance implications and pick the right format for your use case.

~7 min read
UUID vs ULID vs NanoID. Which Unique ID Format Should You Use in 2026 and Why the Wrong Choice Hurts Your Database

You're starting a new service. You need IDs for records in your database. UUID v4 is the obvious default. You reach for it.

Then a senior engineer pushes back. Use ULID instead. UUID v4 fragments your indexes. You search for ULID, read the spec, understand roughly what it is: time-sortable, database-friendly. You add it to the backlog to revisit.

Two weeks later you're reviewing a pull request on the frontend side of the same project. The author has used NanoID. You leave a comment asking why. The response: shorter IDs, URL-safe, widely used in frontend codebases. You merge it.

You now have three different ID formats in the same system. They came from three sensible decisions made independently. None of them is obviously wrong. But they have different properties, different tradeoffs, and different performance implications depending on your database engine and access patterns.

UUID v4: The Universal Default

UUID v4 is a 128-bit random identifier represented as 32 hexadecimal characters: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx, where the 4 and y are fixed version and variant bits.

f47ac10b-58cc-4372-a567-0e02b2c3d479

122 of the 128 bits are random. The probability of two v4 UUIDs colliding is effectively zero for any realistic use case. Every major language and framework has a built-in way to generate them. They're universally supported across databases, ORMs, APIs, and serialization formats.

The problem is what they do to database indexes.

Most relational databases use B-tree indexes for primary keys. A B-tree is ordered. When you insert a record, the database places it at the correct position in the index to maintain sort order. When your primary key is a random UUID, each insert goes to a random position. The database reads a random page, inserts the value, and potentially rebalances the tree. At low volumes this is imperceptible. At high volumes it becomes a bottleneck.

Index fragmentation. Random inserts produce index pages that are partially filled rather than sequentially packed. More pages means more disk reads for range queries and index scans. The problem compounds over time and is difficult to diagnose because the writes still succeed. Performance degrades gradually rather than all at once.

PostgreSQL handles UUID primary keys better than MySQL because of its MVCC architecture and buffer pool behavior. But the fragmentation still accumulates. Teams with high-volume write workloads that switch from random UUIDs to time-sortable IDs consistently report lower write latency and more efficient storage.

UUID v7: The Upgrade That Fixes the Index Problem

UUID v7 was standardized in RFC 9562 in 2024. It replaces the 122 random bits of v4 with a structure that begins with a millisecond-precision Unix timestamp in the most significant bits.

018f6a3b-1c5d-7a2e-8f3d-4b5c6d7e8f9a

Because the timestamp is in the most significant bits, UUIDs generated close in time sort close together in a B-tree index. Sequential inserts become approximately sequential in the index. The fragmentation problem largely disappears. You get the universality and collision resistance of UUID with the database-friendly ordering of a timestamp-prefixed format.

UUID v7 is backward compatible. It's still a valid UUID. Existing UUID columns, validation logic, and libraries handle it without changes. Only the generator changes. If your performance bottleneck is UUID fragmentation and you want the simplest fix without changing your ID format, UUID v7 is the answer.

ULID: Sortable, Compact, Human-Readable

ULID stands for Universally Unique Lexicographically Sortable Identifier. It solves the same problem as UUID v7 but with a different encoding that's more compact and more readable.

01ARZ3NDEKTSV4RRFFQ69G5FAV

26 characters. The first 10 encode a millisecond-precision Unix timestamp using Crockford Base32, a character set that excludes visually ambiguous characters like I, L, O, and U. The remaining 16 characters are random.

ULID sorts correctly as a plain string. Compare two ULID values lexicographically and the one that comes first in string order was generated first in time. This matters in systems where IDs are stored in string columns, sorted in application code, or used as keys in distributed caches. No special UUID type in the database, no custom comparator: standard string sort gives you time order.

ULID also packs more randomness per character than UUID in hex format. The 80 random bits provide sufficient collision resistance for all realistic use cases. The Crockford Base32 alphabet makes it URL-safe without modification.

The tradeoff: UUID is a first-class type in PostgreSQL. There's no native ULID type. You store it as char(26) or text, which is larger than a UUID stored as a binary 16-byte value. For most teams this doesn't matter in practice. But it's worth knowing before you commit to the format.

NanoID: Short, URL-Safe, Not Sortable

NanoID is a different category of tool. It's not designed for database primary keys. It's designed for IDs that need to be short, URL-safe, and unique.

The default NanoID uses 21 characters from an alphabet of 64 URL-safe characters.

V1StGXR8_Z5jdHi6B-myT

It uses crypto.getRandomValues in the browser and crypto.randomFillSync in Node.js: cryptographically random. With 21 characters from 64 possible values, the collision probability for a billion IDs per hour is negligible.

NanoID isn't time-sortable. Fully random, no timestamp component. That makes it unsuitable as a database primary key for any table that benefits from sequential inserts.

Where NanoID genuinely fits: short unique tokens in URLs (password reset links, share links, invite codes), session identifiers where human readability matters, and any context where you want an ID short enough to be visible without being distracting. A 21-character ID is more practical in a URL than a 36-character UUID.

NanoID also supports a configurable alphabet and length. Reduce the length at the cost of higher collision probability, or use a custom character set that excludes characters your system treats as special.

Comparison Table

FormatLengthSortableURL-SafeDatabase-FriendlyStandard
UUID v436 charsNoNoPoor (index fragmentation)RFC 4122
UUID v736 charsYesNoGood (sequential inserts)RFC 9562
ULID26 charsYesYesGood (as text column)ulid.github.io spec
NanoID21 charsNoYesNot intended for PKsNo formal spec

How to Choose

For a new table with expected write volume under a few thousand records per second, UUID v4 is fine. The fragmentation at this volume doesn't produce measurable problems. Use it and move on.

For high-volume write tables or append-heavy tables in PostgreSQL or MySQL, use UUID v7 or ULID. UUID v7 is the simpler migration if you already use UUID columns elsewhere. ULID is better if you want URL-safe IDs, string-comparable IDs, or IDs stored in text columns rather than a native UUID type.

For IDs in URLs that users will see, use NanoID. The default 21 characters are short enough to be readable and long enough to avoid collisions. Don't put NanoIDs in a primary key column that powers range queries or index scans.

For distributed systems where multiple nodes generate IDs concurrently, UUID v4, UUID v7, and ULID all work. ULID has a monotonicity option that ensures IDs generated within the same millisecond on the same node sort in generation order, which matters if you're using IDs as a tiebreaker in event ordering.

For the broadest compatibility across systems, languages, and databases, UUID v4 or UUID v7. Both are recognised by every database, ORM, and API specification that handles UUIDs.

The Default in 2026

For new projects starting today, UUID v7 is the cleanest default for primary keys. It has the universality of UUID, fixes the B-tree fragmentation problem that plagued UUID v4 at scale, and requires no changes to existing UUID column types. The spec is finalized and language support is broad.

For teams already on ULID who find value in its URL-safe format and string sortability, there's no reason to migrate. The performance characteristics are similar to UUID v7 at most write volumes.

For short tokens in URLs, NanoID remains the right choice. The UUID and ULID Generator generates v4, v7, and ULID formats with bulk generation support for fixtures, migrations, and test runs.

Pick the format that matches the access pattern of the data it identifies. Sequential writes to a database primary key: UUID v7 or ULID. Short tokens in URLs: NanoID. Everything else: UUID v4 is fine.

Free Developer Tools

Put the knowledge to work.

40 browser-based tools. No account. No data sent to a server.