UUID v4 / v7 / NanoID / CUID2 — A Complete Comparison Guide
Why Identifier Choice Matters
Identifiers are a foundational decision in distributed systems and database design. A seemingly simple choice — "just use UUID" — can later lead to index fragmentation, poor insertion performance, or unintended data leakage through timestamp exposure.
UUID v4, UUID v7, NanoID, CUID2 — each has a distinct design philosophy. This guide compares them based on their specifications and helps you choose the right one for your use case.
UUID Specification Overview (RFC 9562)
The current UUID specification is RFC 9562, published in May 2024. It obsoletes RFC 4122 and formally defines UUID versions 6, 7, and 8 for the first time.
A UUID is a 128-bit value, commonly represented as a hyphenated hexadecimal string: xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx. The 4-bit version field (M) and variant bits (N) are embedded in the structure.
| Version | Generation Method | Time-Ordered |
|---|---|---|
| v1 | Timestamp + MAC address | Yes (imperfectly) |
| v4 | Fully random | No |
| v7 | Unix Epoch + random | Yes |
UUID v4 — The Standard Random Identifier
Structure
Of UUID v4's 128 bits, 122 bits are random. The remaining 6 bits encode the version (0100) and variant (10).
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
^ ^
version=4 variant bits
Collision Probability
The 122-bit random space contains 2^122 (approximately 5.3 × 10^36) possible values. By the birthday paradox, a 50% collision probability is reached only after generating approximately 2.71 × 10^18 UUIDs. In practice, collisions are negligible without deliberate attack.
// Node.js built-in crypto module
const { randomUUID } = require('crypto');
const id = randomUUID();
// => "f47ac10b-58cc-4372-a567-0e02b2c3d479"
// Browser Web Crypto API
const id = crypto.randomUUID();
Key Characteristics
- Unpredictability: Uses a cryptographically secure pseudorandom number generator (CSPRNG), making consecutive IDs impossible to predict
- No time-ordering: Random insertion positions degrade B-tree index performance at scale
- Universal support: Native support in virtually all languages and frameworks
Best For
- Resources exposed in URLs where creation time should not be inferred
- Small to medium datasets where insertion performance is acceptable
- Projects that prioritize simplicity
UUID v7 — Time-Ordered Random Identifiers
UUID v7, formally defined in RFC 9562, combines a Unix millisecond timestamp with random bits, making it both time-ordered and suitably unpredictable.
Structure
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unix_ts_ms |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unix_ts_ms | ver | rand_a |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var| rand_b |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| rand_b |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
The upper 48 bits carry the Unix millisecond timestamp (unix_ts_ms). The remaining bits consist of the version, variant, and 74 bits of random data (rand_a / rand_b).
// uuidv7 package (npm)
import { uuidv7 } from 'uuidv7';
const id1 = uuidv7(); // "0191c0f2-87b2-7000-b3c5-8a1e3b2d5678"
const id2 = uuidv7(); // "0191c0f2-87b4-7001-a2f3-7c9d0e1f2a3b"
// String comparison: id1 < id2 (chronological order preserved)
Why UUID v7 Is Database-Friendly
Most databases (MySQL InnoDB, SQL Server) use B-tree clustered indexes for primary keys. UUID v4's random values cause frequent mid-page insertions, leading to page splits and fragmentation.
UUID v7's timestamp prefix means new records are almost always appended at the end, dramatically reducing fragmentation. PostgreSQL benchmarks with large datasets report 30–50% faster insertions compared to UUID v4.
// TypeScript: Drizzle ORM with UUID v7
import { uuidv7 } from 'uuidv7';
import { pgTable, uuid, text } from 'drizzle-orm/pg-core';
const events = pgTable('events', {
id: uuid('id').primaryKey().$defaultFn(() => uuidv7()),
name: text('name').notNull(),
});
Best For
- Primary keys on high-write tables
- Records that need time-based sorting or cursor pagination
- Retaining UUID's standard 128-bit format while improving database performance
NanoID — Compact, URL-Safe Identifiers
NanoID by Andrey Sitnik was released in 2017 as a lightweight alternative to UUID. By default it generates a 21-character string using a URL-safe alphabet.
Design Philosophy
NanoID uses a configurable alphabet (default: A-Za-z0-9_-, 64 characters) and configurable length, producing more entropy per character than UUID.
Default (21 chars, 64-char alphabet):
Random bits = 21 × log2(64) = 21 × 6 = 126 bits
This slightly exceeds UUID v4's 122 bits while using only 21 characters.
import { nanoid, customAlphabet } from 'nanoid';
const id = nanoid(); // "V1StGXR8_Z5jdHi6B-myT"
const shortId = nanoid(10); // "IRFa-VaY2b"
// Numbers only
const numericId = customAlphabet('0123456789', 12);
console.log(numericId()); // "483920571038"
// Human-readable (no ambiguous chars like 0/O, 1/l)
const humanId = customAlphabet('ABCDEFGHJKLMNPQRSTUVWXYZ23456789', 8);
console.log(humanId()); // "A7XK3M2P"
Comparison with UUID v4
| Property | UUID v4 | NanoID (default) |
|---|---|---|
| Length | 36 (with hyphens) | 21 |
| Random bits | 122 | 126 |
| URL-safe | Partially (hyphens are safe) | Yes |
| Time-ordered | No | No |
| Bytes (UTF-8) | 36 | 21 |
| Standards body | RFC 9562 | None |
Best For
- IDs used directly in URLs (short link slugs, public resource identifiers)
- Frontend or mobile apps where bundle size matters
- Scenarios where UUID's length is unsuitable for the UI
CUID2 — Collision Resistance for Distributed Systems
CUID2 is a 2022 redesign of the original CUID by Eric Elliott. While the original relied on timestamps and counters, CUID2 uses SHA-3 hashing to eliminate timestamp-based patterns.
Design Goals
- Collision resistance in distributed environments: Multiple servers and processes generating IDs simultaneously should not collide
- No embedded timestamps: The creation time cannot be inferred from the ID
- Fingerprint entropy: Environmental information (e.g., a hash of the hostname) is mixed into the entropy pool
import { createId } from '@paralleldrive/cuid2';
const id = createId(); // "clh3x5y9b0000qzrmn3b24bmi"
// Always lowercase alphanumeric; first character is always a letter
Internal Structure
[first char (letter)] + [SHA-3 hash of (timestamp + fingerprint + random)]
Default length is 24 characters. You can customize both length and fingerprint:
import { init } from '@paralleldrive/cuid2';
const createCustomId = init({
length: 32,
fingerprint: 'my-app-server-1',
});
console.log(createCustomId()); // 32-character string starting with a letter
Best For
- Microservices where many servers generate IDs simultaneously
- Applications where ID creation time must not be exposed
- Systems that require lowercase alphanumeric IDs
Full Comparison Matrix
| Property | UUID v4 | UUID v7 | NanoID | CUID2 |
|---|---|---|---|---|
| Random bits | 122 | 74 | 126 | ~160+ |
| Time-ordered | ✗ | ✓ | ✗ | ✗ |
| URL-safe | Partially | Partially | ✓ | ✓ |
| Specification | RFC 9562 | RFC 9562 | None | None |
| Default length | 36 | 36 | 21 | 24 |
| DB insert efficiency (large scale) | Low | High | Low | Low |
| Timestamp predictability | None | 48-bit timestamp | None | None |
| External dependencies | None | Required | Required | Required |
Choosing a Primary Key for Your Database
Database-Specific Guidance
PostgreSQL: Stores UUID as a native 128-bit type with minimal overhead. UUID v4 performs acceptably at moderate scale, but UUID v7 is worth considering once insertion rates exceed a few thousand per second on indexed tables.
MySQL / MariaDB: InnoDB's clustered index makes UUID v4's random insertions especially costly. UUID v7 or storing UUIDs as BINARY(16) significantly reduces fragmentation.
-- MySQL: store UUID as binary for better performance
CREATE TABLE users (
id BINARY(16) NOT NULL DEFAULT (UUID_TO_BIN(UUID(), 0)),
email VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
-- Retrieve as text
SELECT BIN_TO_UUID(id) AS id, email FROM users;
MongoDB: MongoDB's default ObjectID is a 12-byte value with a timestamp prefix — structurally similar to UUID v7. When switching to UUID in MongoDB, UUID v7 is a natural fit.
Security Consideration
UUID v7's upper 48 bits are a Unix millisecond timestamp. When exposed in URLs, the creation time of the resource can be estimated. If this is unacceptable, use UUID v4 or NanoID instead.
https://example.com/api/items/0191c0f2-87b2-7000-b3c5-8a1e3b2d5678
^^^^^^^^^^^^^^^^
Timestamp: approximately mid-2024, inferrable
Practical Implementation: Node.js / TypeScript
// src/lib/id.ts
import { uuidv7 } from 'uuidv7';
import { nanoid, customAlphabet } from 'nanoid';
import { createId } from '@paralleldrive/cuid2';
// Database primary key (time-ordered, UUID-compatible)
export const generateRecordId = (): string => uuidv7();
// URL slug (short, URL-safe)
export const generateSlug = (length = 10): string => nanoid(length);
// Distributed microservice ID (no timestamp exposure)
export const generateServiceId = (): string => createId();
// Cryptographically random UUID v4 (Node.js built-in, no dependencies)
export const generateSecureId = (): string => crypto.randomUUID();
UUID Generator Tool
You can generate and inspect UUIDs with the UUID Generator. It supports both v4 and v7, including bulk generation.
Summary
| Use Case | Recommended |
|---|---|
| Primary key on a large write-heavy table | UUID v7 |
| Security-sensitive resource IDs | UUID v4 |
| Short, URL-friendly IDs | NanoID |
| Distributed microservice ID generation | CUID2 |
| Standards compliance required | UUID v4 or v7 (RFC 9562) |
No single identifier is universally best. The right choice depends on your database engine, scale, security requirements, and whether the ID will appear in user-facing URLs. UUID v7 is increasingly the default choice for new projects that care about both standards compliance and database performance.
References
- RFC 9562 — Universally Unique IDentifiers (UUIDs) (May 2024, IETF)
- NanoID GitHub Repository — Andrey Sitnik
- CUID2 GitHub Repository — ParallelDrive
- uuidv7 npm package — LiosK
