Skip to Content
core-conceptsPerformance

Last Updated: 3/9/2026


Performance

Nano ID is optimized for production use with excellent performance characteristics.

Benchmark Results

Secure Generators

crypto.randomUUID 7,619,041 ops/sec (fastest) uuid v4 7,436,626 ops/sec @napi-rs/uuid 4,730,614 ops/sec uid/secure 4,729,185 ops/sec @lukeed/uuid 4,015,673 ops/sec nanoid 3,693,964 ops/sec ← Good balance customAlphabet 2,799,255 ops/sec nanoid for browser 380,915 ops/sec secure-random-string 362,316 ops/sec uid-safe.sync 354,234 ops/sec shortid 38,808 ops/sec (slowest)

Non-Secure Generators

uid 11,872,105 ops/sec (fastest) rndm 2,308,044 ops/sec nanoid/non-secure 2,226,483 ops/sec

Test configuration: Framework 13 7840U, Fedora 39, Node.js 21.6

Performance Analysis

Why Nano ID is Fast Enough

At 3.7M operations/second, Nano ID generates:

  • 3,700,000 IDs per second
  • 222 million IDs per minute
  • 13 billion IDs per hour

In practice: Even high-traffic applications rarely need >1000 IDs/sec, making Nano ID’s performance more than adequate.

Why Not Fastest?

Nano ID prioritizes bundle size and URL-safety over raw speed:

LibrarySpeedSizeURL-Safe
crypto.randomUUID7.6M/sNative❌ (needs encoding)
uuid v47.4M/s423 bytes❌ (hyphens)
nanoid3.7M/s118 bytes

Trade-off: 2x slower but 3.5x smaller and URL-safe.

Optimization Techniques

1. Random Pool Architecture

Nano ID uses pooled random generation to minimize system calls:

const POOL_SIZE_MULTIPLIER = 128 function fillPool(bytes) { if (!pool || pool.length < bytes) { // Generate 128x requested size pool = Buffer.allocUnsafe(bytes * 128) crypto.getRandomValues(pool) } }

Impact: 128 IDs generated per system call instead of 1.

2. Bitwise Operations

Uses fast bitwise AND for alphabet mapping:

// Fast: bitwise AND (single CPU cycle) id += urlAlphabet[pool[i] & 63] // Slow: modulo (multiple CPU cycles) id += urlAlphabet[pool[i] % 64]

3. Compact Code

Smaller code = better CPU cache utilization:

  • 118 bytes fits entirely in L1 cache
  • No function call overhead
  • Inline operations

Performance Comparison

vs UUID v4

import { randomUUID } from 'crypto' import { nanoid } from 'nanoid' // UUID: 7.4M ops/sec, 423 bytes const uuid = randomUUID() // "f47ac10b-58cc-4372-a567-0e02b2c3d479" // Nano ID: 3.7M ops/sec, 118 bytes const id = nanoid() // "V1StGXR8_Z5jdHi6B-myT"

When to use UUID:

  • Maximum performance needed
  • UUID format required by system
  • Bundle size doesn’t matter

When to use Nano ID:

  • Bundle size matters (web apps)
  • URL-safe IDs needed
  • Shorter IDs preferred

vs customAlphabet

import { nanoid, customAlphabet } from 'nanoid' // Default: 3.7M ops/sec const id1 = nanoid() // Custom: 2.8M ops/sec (24% slower) const nanoid2 = customAlphabet('0123456789', 10) const id2 = nanoid2()

Why slower: Rejection sampling for non-power-of-2 alphabets.

vs Non-Secure

import { nanoid } from 'nanoid' import { nanoid as insecure } from 'nanoid/non-secure' // Secure: 3.7M ops/sec const id1 = nanoid() // Non-secure: 2.2M ops/sec (40% slower!) const id2 = insecure()

Paradox: Non-secure is slower because:

  • No pooling (calls Math.random() per character)
  • Secure version batches system calls

Real-World Performance

HTTP Server

import { nanoid } from 'nanoid' import express from 'express' const app = express() app.post('/users', (req, res) => { const user = { id: nanoid(), // ~270 nanoseconds ...req.body } // Database insert: ~5-50 milliseconds // Network latency: ~10-100 milliseconds // ID generation is 0.0005% of total request time })

Conclusion: ID generation is negligible compared to I/O.

Batch Generation

import { nanoid } from 'nanoid' // Generate 10,000 IDs const start = Date.now() const ids = Array(10000).fill(null).map(() => nanoid()) const elapsed = Date.now() - start console.log(`Generated 10,000 IDs in ${elapsed}ms`) // Output: ~2.7ms (270 nanoseconds per ID)

Optimization Tips

✅ Do: Reuse Custom Alphabet Generator

import { customAlphabet } from 'nanoid' // ✅ Good: Create once const nanoid = customAlphabet('0123456789', 10) for (let i = 0; i < 10000; i++) { const id = nanoid() // Fast }

❌ Don’t: Recreate Generator

import { customAlphabet } from 'nanoid' // ❌ Bad: Creates generator 10,000 times for (let i = 0; i < 10000; i++) { const nanoid = customAlphabet('0123456789', 10) const id = nanoid() // Slow }

✅ Do: Use Power-of-2 Alphabets

import { customAlphabet } from 'nanoid' // ✅ Fast: 64 chars = 2^6 (no rejection) const fast = customAlphabet( 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_', 21 ) // ❌ Slower: 30 chars (rejection sampling needed) const slow = customAlphabet('0123456789ABCDEFGHIJKLMNOPQR', 21)

✅ Do: Batch Operations

import { nanoid } from 'nanoid' // ✅ Good: Batch database inserts const users = Array(1000).fill(null).map((_, i) => ({ id: nanoid(), name: `User ${i}` })) await db.users.insertMany(users) // Single query

When Performance Matters

High-Throughput Systems

If generating >1M IDs/second:

import { randomUUID } from 'crypto' // Use crypto.randomUUID for maximum speed const id = randomUUID()

Embedded Systems

If CPU-constrained:

import { nanoid } from 'nanoid/non-secure' // Use non-secure for lower CPU usage // (but only if security isn't critical) const id = nanoid()

Bundle Size Critical

If every byte matters:

import { nanoid } from 'nanoid' // Nano ID: 118 bytes // UUID: 423 bytes // Savings: 305 bytes per import

Profiling

Measure ID Generation

import { nanoid } from 'nanoid' const iterations = 100000 const start = performance.now() for (let i = 0; i < iterations; i++) { nanoid() } const elapsed = performance.now() - start const opsPerSec = (iterations / elapsed) * 1000 console.log(`${opsPerSec.toFixed(0)} ops/sec`)

Compare Alternatives

import { nanoid } from 'nanoid' import { randomUUID } from 'crypto' function benchmark(fn, name, iterations = 100000) { const start = performance.now() for (let i = 0; i < iterations; i++) fn() const elapsed = performance.now() - start const opsPerSec = (iterations / elapsed) * 1000 console.log(`${name}: ${opsPerSec.toFixed(0)} ops/sec`) } benchmark(() => nanoid(), 'nanoid') benchmark(() => randomUUID(), 'crypto.randomUUID')

See Also