TTelekit Tools
Security12 min read (2,246 words)

UUID v4 vs UUID v7 vs ULID vs NanoID: Comparison Guide

By Telekit SecurityยทPublished July 15, 2026ยทUpdated October 31, 2026

The Evolution of Primary Keys: UUID v4 vs v7 vs ULID vs NanoID

For decades, auto-incrementing integer IDs (1, 2, 3, 4...) were the universal standard for relational database primary keys. While simple and efficient for single-node architectures, sequential integers suffer from critical architectural and security limitations in modern distributed environments:

  • Security & Business Intelligence Scraping: Predictable sequential IDs expose transaction volume, total user counts, and internal database records to competitors via URL parameter enumeration attacks.
  • Distributed Multi-Master Write Conflicts: Coordinating auto-incrementing counters across multi-region databases or decoupled microservices requires centralized coordination locks, destroying horizontal scalability.
  • Client-Side Generation Impossibility: Web clients and mobile apps cannot generate sequential IDs offline before syncing with central servers.

To resolve these challenges, modern distributed software architectures rely on globally unique identifiers. However, choosing between UUID v4, UUID v7, ULID, and NanoID requires understanding key trade-offs between cryptographic entropy, database B-Tree index cache efficiency, lexicographical sortability, and URL string compactness.

You can generate, inspect, and benchmark all four identifier formats live in your browser using our client-side **UUID & ULID Generator, or explore clustered B-Tree write metrics in our UUID v4 vs UUID v7 Comparison Guide**.

Complete Comparison Matrix: UUID v4 vs v7 vs ULID vs NanoID

IdentifierBit Width & Encoded LengthTotal Entropy / Collision ResistanceTime-Sortable?Standard / SpecificationCanonical AlphabetIdeal Use Cases
UUID v4128 bits (36 chars with hyphens)122 bits pseudo-random entropyโŒ No (Purely Random)RFC 4122 / RFC 9562Hexadecimal (0-9, a-f)Ephemeral session tokens, distributed event IDs, non-indexed payloads
UUID v7128 bits (36 chars with hyphens)48-bit Unix ms timestamp + 74 bits randomโœ… Yes (Unix millisecond order)RFC 9562 (IETF Standard)Hexadecimal (0-9, a-f)Primary keys in PostgreSQL, MySQL, SQLite, and distributed SQL databases
ULID128 bits (26 characters)48-bit Unix ms timestamp + 80 bits randomโœ… Yes (Lexicographically sortable)Defacto Open Spec (alizain/ulid)Crockford's Base32 (Case-insensitive)URL route slugs, DynamoDB partition keys, Kafka message keys
NanoIDConfigurable (Default 126 bits / 21 chars)126 bits cryptographic entropyโŒ No (Purely Random)Open Source (ai/nanoid)URL-Safe Base64 (A-Za-z0-9_-)Short URLs, public invitation codes, DOM elements, frontend state tokens

UUID v4: Pure Randomness & RFC 4122 Standard

UUID v4 is the most ubiquitous 128-bit identifier in computing. Standardized in RFC 4122 (and reaffirmed in RFC 9562), UUID v4 generates 122 cryptographically secure random bits with 6 fixed version and variant bits:

code4 lines
f47ac10b-58cc-4372-a567-0e02b2c3d479
             ^    ^
             โ”‚    โ””โ”€โ”€ Variant 1 (RFC 4122): bits '10xx' (8, 9, a, or b)
             โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Version 4: 4 bits '0100'

When to Use UUID v4: - Stateless Tokens & Nonces: Perfect for OAuth state tokens, CSRF tokens, and one-time password reset nonces. - Zero Timestamp Leakage: Does not embed generation timestamps, ensuring complete temporal privacy. - Universal Native Support: Standard library support in almost every programming language (e.g. crypto.randomUUID() in JavaScript, uuid.uuid4() in Python).

UUID v7: The Modern Relational Database Standard (RFC 9562)

Approved by the IETF in RFC 9562, UUID v7 represents the modern evolution of unique identifiers for database systems. UUID v7 combines the standardized 36-character hyphenated format and 128-bit size of traditional UUIDs with a 48-bit millisecond Unix timestamp:

code2 lines
018e3a2b-7c40-7f12-8921-998877665544
โ””โ”€โ”€ 48-bit Timestamp โ”€โ”€โ”˜ โ””โ”€ Ver/Var + 74 Random bits โ”€โ”˜

Key Architectural Advantages of UUID v7: - RFC Standard Compliance: Officially recognized across database drivers, ORMs, and serialization protocols. - Sub-Millisecond Monotonicity: Optional counter bits guarantee strict ordering for multiple IDs generated in the exact same millisecond. - Native 128-bit DB Storage: Fits cleanly into native PostgreSQL UUID and MySQL BINARY(16) database column types without custom schema migrations.

B-Tree Index Fragmentation: UUID v4 vs UUID v7 Performance Benchmark

The most significant distinction between UUID v4 and UUID v7 lies in how relational database storage engines (such as PostgreSQL's nbtree and MySQL InnoDB's clustered B+ Trees) organize data on physical NVMe/SSD storage.

Why Random UUID v4 Destroys Index Performance: - Random Leaf Node Insertion: Because UUID v4 values have uniform random distribution, new rows are inserted at random page locations throughout the entire B-Tree index. - Frequent Page Splits: When an index leaf page fills up (typically 8KB or 16KB), the database must split the page in half, rewrite sibling pointers, and flush two separate physical disk blocks. - Buffer Pool Thrashing: As table size exceeds available RAM, every insert requires reading a random page from disk into cache, evicting active pages and causing high I/O wait times.

How UUID v7 Restores Fast Append-Only Insertion: Because the first 48 bits of UUID v7 represent a monotonically increasing Unix timestamp, every newly inserted record lands naturally on the rightmost leaf page of the B-Tree index. This eliminates mid-tree page splits, keeps active index pages hot in RAM, and achieves insertion throughput comparable to sequential integer primary keys (typically 40% to 60% faster insert speeds with up to 30% reduction in index bloat).

ULID: Lexicographically Sortable Crockford Base32 Identifier

ULID (Universally Unique Lexicographically Sortable Identifier) is a 128-bit identifier formatted as 26 uppercase characters using Crockford's Base32 alphabet (0123456789ABCDEFGHJKMNPQRSTVWXYZ):

  • 48-Bit Timestamp: First 10 characters encode millisecond Unix time (sufficient until the year 10889 AD).
  • 80-Bit Randomness: Final 16 characters provide cryptographic randomness with monotonic incrementing fallback.
  • Crockford's Base32 Readability: Excludes confusing characters (I, L, O, U) to eliminate ambiguity in human reading and voice transcription.
  • Case-Insensitive & URL-Safe: Contains no hyphens, slashes, or URL special characters, making it clean in REST API route parameters (/api/orders/01ARZ3NDEKTSV4RRFFQ69G5FAV).

NanoID: Compact, URL-Safe & Collision-Resistant Identifier

NanoID is a lightweight, customizable string identifier designed for web applications where character efficiency is paramount:

  • 21 Characters Default: Delivers 126 bits of entropy (comparable to UUID v4) in a 40% shorter string length.
  • URL-Safe Alphabet: Uses A-Za-z0-9_- without requiring URL encoding or character escaping.
  • Configurable Alphabet & Length: Developers can define custom character sets (e.g. numeric-only 0-9, lowercase alphanumeric) and adjust lengths dynamically.
  • Tiny Bundle Size: Under 130 bytes in JavaScript bundles, making it ideal for high-performance frontend applications.

How to Choose Between UUID v4, v7, ULID, and NanoID (Step-by-Step)

Step 1: Evaluate Database Storage and B-Tree Indexing Requirements If your identifier serves as a primary key or foreign key in a relational database (PostgreSQL, MySQL, SQLite, Oracle) where writes exceed tens of thousands of records, select UUID v7. Its monotonically increasing 48-bit timestamp prefix ensures append-only index leaf writes, preventing severe B-Tree page splits and disk I/O thrashing. If you do not require database indexing, proceed to Step 2.

Step 2: Assess Privacy and Creation Timestamp Leakage Risks Determine whether your application can safely disclose the exact millisecond an identifier was created. Both UUID v7 and ULID embed timestamp metadata in their first bits. If competitors could exploit creation times to deduce user registration rates, daily order volumes, or system activity, avoid time-sorted IDs and choose UUID v4 or cryptographically hashed tokens.

Step 3: Determine URL Aesthetics and String Length Budget Analyze where the identifier appears in your architecture. If the ID is exposed to humans in SMS messages, short URL slugs, QR codes, or mobile screens, a 36-character hyphenated UUID creates visual clutter. For compact user-facing links, choose NanoID (21 characters) or ULID (26 characters). ULID offers the added advantage of Crockford Base32 case-insensitivity without ambiguous characters like O and 0.

Step 4: Verify Multi-Language and ORM Standardization Support Review your technology stack across backend services, third-party libraries, and ORMs (Prisma, TypeORM, Drizzle, Hibernate, SQLAlchemy). UUID v4 has 100% universal standard library support across every language. UUID v7 is standardized under IETF RFC 9562 and natively supported in modern database drivers. ULID and NanoID require third-party libraries in languages without native implementations.

Step 5: Test and Benchmark Locally Using Client-Side Tools Before committing database schema migrations, benchmark collision rates and inspect identifier structures using our free client-side developer utilities. Use the **UUID & ULID Generator** to generate sample batches of UUID v4, UUID v7, ULID, and NanoID strings, validate timestamp decoding, and export SQL seeds for performance testing in your staging environment.

Multi-Language Code Implementations (JS/TS, Python & Go)

1. JavaScript & TypeScript (Web Crypto API & RFC 9562)

typescript38 lines
// 1. Native UUID v4 (Zero dependencies, Node 14.17+ & modern browsers)
export function generateUUIDv4(): string {
  return crypto.randomUUID();
}

// 2. Pure TypeScript UUID v7 Generator (RFC 9562 compliant)
export function generateUUIDv7(): string {
  const timestamp = Date.now();
  const bytes = new Uint8Array(16);
  crypto.getRandomValues(bytes);

  // 48-bit timestamp in big-endian
  bytes[0] = (timestamp / 0x10000000000) & 0xff;
  bytes[1] = (timestamp / 0x100000000) & 0xff;
  bytes[2] = (timestamp / 0x1000000) & 0xff;
  bytes[3] = (timestamp / 0x10000) & 0xff;
  bytes[4] = (timestamp / 0x100) & 0xff;
  bytes[5] = timestamp & 0xff;

  // Version 7 (0b0111)
  bytes[6] = (bytes[6] & 0x0f) | 0x70;
  // Variant 1 RFC 4122 (0b10xx)
  bytes[8] = (bytes[8] & 0x3f) | 0x80;

  const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
}

// 3. NanoID Implementation with Web Crypto
export function generateNanoId(size = 21): string {
  const alphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLFGQZ_flavor";
  const bytes = crypto.getRandomValues(new Uint8Array(size));
  let id = "";
  for (let i = 0; i < size; i++) {
    id += alphabet[bytes[i] & 63];
  }
  return id;
}

2. Python 3 (Standard Library & Modern UUID Generators)

python24 lines
import uuid
import time
import os

# 1. UUID v4 (Standard Library)
v4_id = uuid.uuid4()
print("UUID v4:", v4_id)

# 2. UUID v7 (RFC 9562 via Python standard libraries)
def generate_uuid7() -> uuid.UUID:
    ns = time.time_ns()
    timestamp_ms = ns // 1_000_000
    rand_bytes = bytearray(os.urandom(10))
    
    # 48-bit millisecond timestamp
    time_bytes = timestamp_ms.to_bytes(6, byteorder='big')
    raw = bytearray(time_bytes + rand_bytes)
    
    # Set version 7 (0b0111) and variant 1 (0b10xx)
    raw[6] = (raw[6] & 0x0F) | 0x70
    raw[8] = (raw[8] & 0x3F) | 0x80
    return uuid.UUID(bytes=bytes(raw))

print("UUID v7:", generate_uuid7())

3. Go (google/uuid & RFC 9562 Native Support)

go21 lines
package main

import (
	"fmt"
	"github.com/google/uuid"
)

func main() {
	// 1. UUID v4 (Cryptographic Random)
	v4, err := uuid.NewRandom()
	if err == nil {
		fmt.Println("UUID v4:", v4.String())
	}

	// 2. UUID v7 (Time-Ordered RFC 9562)
	v7, err := uuid.NewV7()
	if err == nil {
		fmt.Println("UUID v7:", v7.String())
		fmt.Println("V7 Timestamp Unix:", v7.Time().UnixTime())
	}
}

4. SQL & Relational Databases (PostgreSQL, MySQL & SQLite)

sql19 lines
-- PostgreSQL with native UUID type
CREATE TABLE orders (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Legacy UUID v4
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- PostgreSQL with UUID v7 extension or native function (Postgres 17+)
CREATE TABLE orders_v7 (
    id UUID PRIMARY KEY, -- Populated with UUID v7 for sequential B-tree writes
    customer_id UUID NOT NULL,
    total_amount NUMERIC(10, 2) NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- MySQL 8+ optimized 16-byte binary primary key
CREATE TABLE users (
    id BINARY(16) PRIMARY KEY, -- Stores raw 128-bit UUID v7
    email VARCHAR(255) NOT NULL UNIQUE
);

Security, Privacy & Timestamp Leakage Considerations

When adopting time-ordered identifiers like UUID v7 and ULID, security engineers must evaluate timestamp disclosure risks:

  • Timestamp Leakage: Because UUID v7 and ULID embed millisecond creation timestamps in the upper 48 bits, anyone who observes the identifier can deduce exactly when the account, document, or transaction was created.
  • When Timestamp Leakage is Unacceptable: For password reset tokens, anonymous voting records, and confidential user IDs, use UUID v4 or cryptographically hashed nonces.
  • Client-Side Generation Privacy: Generating UUIDs and ULIDs in-browser ensures zero external network leakage. Telekit Tools generates all identifiers 100% client-side via the browser's Web Crypto API with zero server logging.

Enhance your identifier and cryptographic workflow with these client-side utilities:

Frequently Asked Questions

What is the difference between UUID v4, UUID v7, ULID, and NanoID? UUID v4 provides 122 bits of pure cryptographic randomness across 36 characters. UUID v7 combines a 48-bit millisecond Unix timestamp with 74 random bits in the standard 36-character format for database B-Tree index optimization. ULID encodes 128 bits into 26 case-insensitive Crockford Base32 characters. NanoID defaults to 21 characters of URL-safe Base64 randomness with configurable alphabets for lightweight frontend tokens.

Why does UUID v4 cause database B-Tree index fragmentation? UUID v4 values are uniformly random, causing newly inserted records to be scattered across arbitrary locations throughout the database B-Tree index. As tables exceed available RAM, this causes frequent disk page splits, low cache hit ratios, and elevated disk write I/O. UUID v7 resolves this by appending new records sequentially at the end of the index.

How does UUID v7 solve database index performance problems? UUID v7 prefixes a 48-bit Unix epoch millisecond timestamp to each identifier. In B-Tree indexes (such as PostgreSQL nbtree and MySQL InnoDB B+ Trees), newly generated records naturally sort sequentially on the rightmost leaf page. This eliminates mid-tree page splits, keeps recent index blocks cached in RAM, and speeds up insert throughput by 40% to 60%.

When should I choose NanoID instead of UUID v4 or UUID v7? Choose NanoID when you need compact, URL-safe identifiers (21 characters vs 36 for UUID), custom character alphabets (like numeric-only or alphanumeric without symbols), or minimal JavaScript client bundle size. Use UUID v7 when database primary key B-Tree indexing and RFC standardization are your primary concerns.

Can UUID v7 timestamps leak sensitive creation times or business metrics? Yes. Because the first 48 bits of UUID v7 represent millisecond Unix time, anyone possessing the ID can decode the exact date and time it was created. While this is advantageous for database indexing and audit sorting, you should use UUID v4 or HMAC-signed tokens if creation time confidentiality is required.

Is ULID case-sensitive, and why does it use Crockford Base32? ULID is case-insensitive. It uses Crockford's Base32 alphabet, which deliberately excludes the characters I, L, O, and U to prevent human transcription errors and misreadings (such as confusing '1' and 'I' or '0' and 'O'). This makes ULID exceptionally safe for URLs, printed identifiers, and voice communication.

Is UUID v7 backward compatible with UUID v4 in databases like PostgreSQL? Yes. UUID v7 shares the identical 128-bit size and 36-character 8-4-4-4-12 hexadecimal hyphenated structure as UUID v4. Relational databases like PostgreSQL store both formats in standard UUID columns without schema changes. You can transition an existing table from UUID v4 to UUID v7 seamlessly for all newly inserted rows.

Can NanoID or ULID collide more easily than UUID v4? No. Default 21-character NanoID provides 126 bits of entropy, which is mathematically comparable to UUID v4 (122 bits of entropy). Generating 1 billion NanoIDs per second for a year yields approximately a 1% collision probability. ULID provides 80 bits of random entropy per millisecond with monotonic sequence fallback, ensuring strict uniqueness even under heavy concurrency.

Are identifiers generated on Telekit Tools secure and private? Yes. All UUIDs, ULIDs, and random sequences generated on Telekit Tools use the browser's native Web Crypto API (crypto.getRandomValues and crypto.randomUUID). Processing is executed 100% locally on your client machine with zero server communication, logging, or third-party storage.