UUID vs NanoID: which ID should you use?
Short answer
Use UUIDv7 for database primary keys and NanoID for short public strings like URL slugs and share codes. A default NanoID is 21 characters to a UUID's 36, with 126 random bits to UUIDv4's 122, so collisions are equally a non-issue. But NanoID is fully random: in our 2-million-row SQLite test, NanoID keys took 17.4 seconds to insert, UUIDv4 21.6 seconds, and time-ordered UUIDv7 2.6 seconds.

Part of the article Pick a UUIDv7 library that keeps order: 10 tested, .NET fails 50%. This page answers one question from it in depth.
On this page · 10 sections
NanoID's win is length, not speed. Stored as text, it made the smallest table (133.5 MB vs 198 MB for UUID text). Stored as a native 16-byte uuid column, UUID is smaller still.
Key facts
| What | UUID (v4 / v7) | NanoID (default) | Source (checked Oct 24 2026) |
|---|---|---|---|
| Standard | IETF RFC 9562 (May 2024) | Library convention (npm nanoid, 2017) |
RFC 9562, ai/nanoid |
| Text length | 36 characters (hex + hyphens) | 21 characters | same |
| Alphabet | 0-9, a-f | A-Z, a-z, 0-9, _, - (64 symbols) |
same |
| Random bits | 122 (v4), 74 (v7) | 126 | same |
| Binary form | 16 bytes, native uuid types |
none, stored as text | same |
| Sortable by time | v7 yes, v4 no | no | same |
| 2M inserts, SQLite text key | v4 21.6 s, v7 2.6 s | 17.4 s | our benchmark |
| Table size, rowid / WITHOUT ROWID | 198.0 / 101.4 MB | 133.5 / 66.6 MB | our benchmark |
| npm downloads, Oct 16 to 22 2026 | uuid 187.3M |
nanoid 178.2M |
npm API |
Download the data (CSV): all six benchmark runs.
How each one is built
UUIDv4 is 128 bits: 122 random, plus 4 version bits and 2 variant bits, printed as xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx. UUIDv7 swaps the first 48 bits for a millisecond Unix timestamp, so new IDs sort after old ones (v7 vs v4 explained).
NanoID has no layout at all. It draws 21 random bytes from the platform's secure generator, keeps 6 bits of each and maps them to a 64-symbol alphabet: V1StGXR8_Z5jdHi6B-myT. Because 64 divides 256 evenly, there is no modulo bias. Andrey Sitnik's library also ships customAlphabet() for other alphabets and lengths, which uses a mask and rejects out-of-range bytes to stay unbiased.
Collision calculator
Change the length or alphabet to see how many IDs it takes to reach a 1% chance of any two matching (birthday bound). Nothing leaves your browser.
At the default 21 characters and 1,000 IDs an hour, a 1% chance takes about 149 billion years, the same figure as the Nano ID collision calculator by GitHub user zelark that the library's README links to. Cut it to 10 characters and the 1% point arrives after about 152 million IDs; 8 characters, about 2.4 million.
Our benchmark
We inserted 2 million IDs of each kind into a SQLite table with the ID as a text primary key, in 50,000-row transactions, twice: once as a normal rowid table (the key gets a separate index) and once WITHOUT ROWID (the table is the index). Python 3.12.10's sqlite3, journaling off, on a Windows 11 desktop. UUIDv7 came from our own RFC 9562 generator; NanoID from the same algorithm as the library.

What it shows:
- Order beats length. NanoID is 42% shorter than a UUID string yet took only about 20% less time than UUIDv4 and 6.7 times slower than UUIDv7. Random keys force the database to write into the middle of its B-tree; time-ordered keys append to the end. The same effect is bigger at scale, as our 5-million-row v4 vs v7 test showed.
- Length still saves disk. As text, NanoID used about a third less space. If your IDs live as strings anyway (JSON APIs, document stores, URLs), that is real.
- Binary beats both. Postgres stores a
uuidin 16 bytes; a 21-character text value takes 22 (a 1-byte header plus the characters). Our UUIDv7 vs ULID page measured text vs bytes directly.
Which should you use?
- Database primary key: UUIDv7 in a native
uuidcolumn. Sortable, standard, and Postgres 18 generates it withuuidv7(). - Public short links, invite codes, file names: NanoID, or a custom alphabet without look-alike characters.
- Both: a UUIDv7 key inside, a NanoID
public_idcolumn outside. Common in apps that do not want sequential or time-revealing IDs in URLs (UUIDv7 leaks its creation time; here is how to read it).
Common misconceptions

"NanoID is basically as popular as UUID, so everyone is choosing it."
The recordThe record: nanoid had 178.2M weekly npm downloads to uuid's 187.3M, but much of that comes as a dependency: PostCSS, which nearly every front-end build uses, depends on nanoid. What is true: it is the most-installed ID library after uuid.
"Shorter IDs collide sooner, so NanoID is riskier."
The recordThe record: at its default length it has 4 more random bits than UUIDv4. What is true: shortened NanoIDs (8 to 12 characters) do collide at realistic volumes; check with the calculator above.
"NanoID is faster, so it makes the app faster."
The recordThe record: the library is faster at generating IDs than crypto.randomUUID() per its own benchmark, but generation takes nanoseconds; the database index is where the time goes, and there NanoID behaves like UUIDv4.
Related
- UUIDv7 vs UUIDv4: which should you use?
- UUIDv7 vs ULID
- How to extract the timestamp from a UUIDv7
- Tool: UUIDv7 generator and decoder
- Article: We tested the UUIDv7 libraries
FAQ
What is the difference between UUID and NanoID?
A UUID is a 128-bit ID standardized in RFC 9562 and usually written as 36 hex characters with hyphens; version 4 has 122 random bits. NanoID is a string generator from the nanoid JavaScript library: by default 21 characters from a 64-symbol URL-safe alphabet (A-Z, a-z, 0-9, underscore and hyphen), which gives 126 random bits. NanoID has no version bits, no timestamp and no standard binary form.
Is NanoID more likely to collide than UUID?
No. A default 21-character NanoID has 126 random bits against UUIDv4's 122, so it is slightly less likely to collide. You would need about 1.3 quintillion NanoIDs (roughly 149 billion years at 1,000 IDs an hour) for a 1% chance of one collision.
Is NanoID good as a database primary key?
It works, but it is fully random, so inserts land all over the index. In our test on a 2-million-row SQLite table, NanoID keys took 17.4 seconds to insert against 2.6 seconds for time-ordered UUIDv7. If you want short public IDs, a common pattern is a UUIDv7 primary key plus a NanoID column for URLs.
Can I make NanoID shorter?
Yes, nanoid(10) gives 10 characters, but collision odds rise fast: 10 characters is 60 bits, and a 1% chance of a collision arrives after about 152 million IDs. Use the calculator on this page before shortening.
Is NanoID secure?
The default nanoid() uses the platform's cryptographically secure random generator, so IDs are unpredictable. The separate nanoid/non-secure export uses Math.random and should not be used where guessing an ID would expose data.
Sources
All checked October 24, 2026.
Specs and libraries
- IETF, RFC 9562: Universally Unique IDentifiers (UUIDs) (May 2024): UUIDv4 and UUIDv7 layouts.
- Andrey Sitnik and contributors, ai/nanoid on GitHub (MIT license, 27.1k stars): default alphabet and length, 126 vs 122 random bits, customAlphabet, the non-secure export, and the library's own generation benchmark.
- zelark, Nano ID collision probability calculator, linked from the nanoid README.
- PostCSS, package metadata on npm (8.5.31): depends on nanoid ^3.3.20.
- PostgreSQL, UUID functions (uuidv7() since PostgreSQL 18) and character types storage (1-byte header for short strings).
Data
- npm registry downloads API, last week (October 16 to 22, 2026): nanoid, uuid, @paralleldrive/cuid2, ulid, uuidv7.
Our data
- data.csv: our benchmark, October 24, 2026. 2,000,000 IDs per run, inserted with executemany in 50,000-row transactions into
CREATE TABLE t(id TEXT PRIMARY KEY, n INTEGER)with and withoutWITHOUT ROWID; Python 3.12.10 sqlite3, journal_mode=off, synchronous=off; one run each, Windows 11 desktop. UUIDv4 from Python's uuid.uuid4(); UUIDv7 from our own RFC 9562 generator (48-bit ms timestamp, 74 random bits); NanoID as 21 secure random bytes masked to 6 bits over A-Za-z0-9_-. Collision figures use the birthday bound n = sqrt(2 x ln(1/0.99) x alphabet^length) (our math).
Images
- All three charts rendered by Tensorust with the Proof Engine from our benchmark and npm data.
Next question
Every number on this page links to its source and was checked on Oct 24, 2026. Spot an error or a newer figure? Email matt@tensorust.com and it gets fixed. Related: UUIDv7 generator and decoder, Snowflake ID decoder.
Read next
- Article · Oct 23, 2026Pick a UUIDv7 library that keeps order: 10 tested, .NET fails 50%
- Article · Oct 18, 2026Build a database in Rust: AI passed 866K tests, then failed ROLLBACK
- Explainer · Oct 24, 2026Guideline 4.3(a) design spam, explained
- Explainer · Oct 24, 2026Snowflake ID vs UUID: which should you use?
Get the next one by email.
What my AI agents tried this week, with the real numbers: what worked, what flopped, what made money. One email when there is something worth reading.
No spam, never sold. Every email has a one-click unsubscribe.