Complete guide to UUIDs with generation examples and best practices
Developer Guide

UUID Complete Guide: Generate, Validate & Use Unique Identifiers

50 min read
3587 words
Share:

UUID Complete Guide: Generate, Validate & Use Unique Identifiers

You added a new user to your database. You gave them ID 12345. Two seconds later, another server added a different user with the same ID.

Your database just exploded.

Or maybe you’re using auto-incrementing integers and someone just scraped your entire user base by iterating from 1 to 999999.

Your security team is not happy.

Or perhaps you’re building a distributed system where three different services need to create records simultaneously without coordinating IDs.

Auto-increment isn’t going to cut it.

This is where UUIDs save the day. But here’s the problem: most developers just slap UUID.randomUUID() everywhere and call it done. Then they wonder why their database performance tanked, why v1 UUIDs are leaking MAC addresses, or why their “unique” identifiers somehow collided.

UUIDs seem simple—just random strings, right? Wrong. There are five different versions, each designed for specific use cases. Storing them wrong can kill your query performance. Using the wrong version can create privacy leaks. And there are newer alternatives like ULID that might be better for your needs.

This guide cuts through the confusion. You’ll learn exactly which UUID version to use, how to generate them in any language, how to store them efficiently in databases, how to validate them properly, and when alternatives like ULID or NanoID make more sense.

By the end, you’ll understand the collision probability math, know how to optimize UUID indexes, and have production-ready code for every major language and framework.

Quick Answer: UUID Essentials

Don’t have time for 7,000 words? Here’s what you need to know:

  • What UUIDs are: 128-bit universally unique identifiers that can be generated independently without coordination
  • Most common version: UUID v4 (random) - use this unless you have a specific reason not to
  • UUID vs GUID: Same thing, different names (GUID is Microsoft’s term)
  • Key benefit: Generate unique IDs across distributed systems without a central authority
  • Common pitfall: Using v1 (leaks MAC address and timestamp) instead of v4
  • Storage tip: Store as BINARY(16) in databases, not VARCHAR(36)
  • Quick tool: Use our UUID Generator to create and validate UUIDs instantly
  • Collision risk: Astronomically low - you’re more likely to win the lottery twice

Still here? Let’s master UUIDs.

What is a UUID?

A UUID (Universally Unique Identifier) is a 128-bit number designed to be unique across all computers and all time—no central coordination required.

UUID Format and Structure

Standard UUID format:

550e8400-e29b-41d4-a716-446655440000

Breaking it down:

550e8400 - e29b - 41d4 - a716 - 446655440000
   ↓        ↓      ↓      ↓          ↓
time-low  time  time  clock    node
          mid   hi    seq

The format:

  • Total length: 36 characters (32 hex digits + 4 hyphens)
  • Without hyphens: 32 hexadecimal characters
  • Bits: 128 bits of data
  • Representation: Usually lowercase, but case-insensitive

Example variations:

// Standard format (RFC 4122)
"550e8400-e29b-41d4-a716-446655440000"

// Without hyphens
"550e8400e29b41d4a716446655440000"

// Uppercase (also valid)
"550E8400-E29B-41D4-A716-446655440000"

// URN format
"urn:uuid:550e8400-e29b-41d4-a716-446655440000"

Why Developers Use UUIDs

Problem #1: Auto-increment fails in distributed systems

-- Server A creates user
INSERT INTO users VALUES (1, 'John');

-- Server B creates user at the same time
INSERT INTO users VALUES (1, 'Jane');  -- ❌ Collision!

Solution with UUID:

-- Server A
INSERT INTO users VALUES ('550e8400-e29b-41d4-a716-446655440000', 'John');

-- Server B (simultaneously)
INSERT INTO users VALUES ('7c9e6679-7425-40de-944b-e07fc1f90ae7', 'Jane');
-- ✅ No collision!

Problem #2: Predictable IDs are a security risk

User profile: https://example.com/users/12345
            → https://example.com/users/12346  // Easy to guess!
            → https://example.com/users/12347

Solution:

User profile: https://example.com/users/550e8400-e29b-41d4-a716-446655440000
            → https://example.com/users/...?  // Impossible to guess next ID

Problem #3: ID generation requires database roundtrip

// Create user - must wait for database to assign ID
const user = await db.insert({name: 'John'});
const userId = user.id;  // Only available after insert

// Can't reference this user until after insert completes

Solution:

// Generate UUID immediately
const userId = generateUUID();

// Use it before database insert
await createUserFolder(userId);
await sendWelcomeEmail(userId);
await db.insert({id: userId, name: 'John'});  // Insert with known ID

Key benefits:

  1. No coordination needed - Every system can generate IDs independently
  2. Merge-friendly - Combine databases from different sources without ID conflicts
  3. Privacy-preserving - Can’t enumerate resources (unlike sequential IDs)
  4. Offline generation - Create IDs without database connection
  5. Distributed systems - Perfect for microservices, replication, sharding

UUID vs GUID: What’s the Difference?

Short answer: They’re the same thing.

Term Used By Notes
UUID Everyone else, RFC 4122 standard Universal standard
GUID Microsoft, .NET, Windows Globally Unique Identifier

Format comparison:

// .NET GUID
Guid id = Guid.NewGuid();
// 550e8400-e29b-41d4-a716-446655440000

// Same as UUID in other languages
const uuid = crypto.randomUUID();
// 550e8400-e29b-41d4-a716-446655440000

The only technical difference is byte ordering in some Microsoft implementations, but modern systems handle this automatically.

Use “UUID” in code and documentation - it’s the universal standard term.

Tools: Generate UUIDs instantly with our UUID Generator and validate formats with our UUID Validator.

UUID Versions: v1, v4, v5 (Which to Use When)

There are five UUID versions, but you’ll mainly use three. Each has specific trade-offs.

UUID Version 1: Timestamp-Based

How it works:

  • Combines current timestamp + MAC address
  • Guarantees uniqueness through hardware address
  • Sortable by creation time

Generation example:

// Node.js with uuid library
const { v1: uuidv1 } = require('uuid');

const id = uuidv1();
// 6ba7b810-9dad-11d1-80b4-00c04fd430c8
//          ↑
//    Timestamp embedded here

Advantages:

  • ✅ Naturally sortable by time
  • ✅ Guaranteed unique (includes MAC address)
  • ✅ Can extract timestamp

Disadvantages:

  • Privacy leak - Exposes MAC address
  • Security risk - Reveals when record was created
  • ❌ Requires system clock
  • ❌ Not truly random

Real-world problem:

const uuid = uuidv1();
// 6ba7b810-9dad-11d1-80b4-00c04fd430c8
//                           ↑
// This is your MAC address - anyone can see it!

// Attacker can:
// 1. Identify your hardware
// 2. Track when you created records
// 3. Correlate activity across services

When to use v1:

  • 🟢 Internal systems where MAC address doesn’t matter
  • 🟢 When you need time-ordered IDs
  • 🔴 Never for public-facing IDs
  • 🔴 Never for sensitive data

UUID Version 4: Random (Most Common)

How it works:

  • 122 bits of randomness
  • 6 bits for version/variant info
  • Statistically guaranteed to be unique

Generation example:

// JavaScript (modern browsers & Node.js 15+)
const uuid = crypto.randomUUID();
// 7c9e6679-7425-40de-944b-e07fc1f90ae7

// Node.js with uuid library
const { v4: uuidv4 } = require('uuid');
const id = uuidv4();
// 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d

Advantages:

  • No privacy concerns - Completely random
  • Simple - Just random numbers
  • Stateless - No system state needed
  • Secure - Can’t predict next ID

Disadvantages:

  • ❌ Not sortable
  • ❌ Slightly larger collision probability (still astronomically low)
  • ❌ Random database index performance

Collision probability:

To have a 50% chance of collision, you need to generate:
2.71 quintillion (2.71 × 10^18) UUIDs

For perspective:
- If you generate 1 billion UUIDs per second
- It would take 85 years to have 50% collision chance

When to use v4:

  • 🟢 Default choice - Use this unless you have a specific reason not to
  • 🟢 Public-facing identifiers
  • 🟢 User IDs, session IDs, API keys
  • 🟢 Distributed systems
  • 🟢 When privacy matters

UUID Version 5: Hash-Based (Deterministic)

How it works:

  • SHA-1 hash of namespace + name
  • Same input always produces same UUID
  • Useful for generating consistent IDs

Generation example:

const { v5: uuidv5 } = require('uuid');

// Predefined namespaces
const DNS_NAMESPACE = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
const URL_NAMESPACE = '6ba7b814-9dad-11d1-80b4-00c04fd430c8';

// Always generates the same UUID for the same input
const id1 = uuidv5('example.com', DNS_NAMESPACE);
// 9073926b-929f-31c2-abc9-fad77ae3e8eb

const id2 = uuidv5('example.com', DNS_NAMESPACE);
// 9073926b-929f-31c2-abc9-fad77ae3e8eb  (same!)

Use cases:

// Generate consistent IDs for external resources
function getResourceId(url) {
  return uuidv5(url, URL_NAMESPACE);
}

getResourceId('https://example.com/page');
// Always returns: a3bb189e-8bf9-3888-9912-ace4e6543002

// Perfect for caching, deduplication
const cache = new Map();
const resourceId = getResourceId(url);
if (cache.has(resourceId)) {
  return cache.get(resourceId);
}

Advantages:

  • Deterministic - Same input = same UUID
  • ✅ Perfect for deduplication
  • ✅ Reproducible across systems
  • ✅ No state required

Disadvantages:

  • ❌ Predictable (if namespace is known)
  • ❌ Requires namespace UUID
  • ❌ Not suitable for secrets

When to use v5:

  • 🟢 Generating IDs from existing data (URLs, email addresses)
  • 🟢 Deduplication systems
  • 🟢 Content-addressable storage
  • 🟢 When you need reproducible IDs
  • 🔴 Never for security-sensitive IDs

Version Comparison Table

Version Uniqueness Privacy Sortable Use Case
v1 Hardware + time ❌ Leaks MAC ✅ Yes Internal time-ordered IDs
v4 Random ✅ Safe ❌ No Default - use this
v5 Hash-based ⚠️ Depends ❌ No Deterministic IDs from data

Decision flowchart:

Need deterministic IDs from data?
  ↓ Yes → Use v5
  ↓ No
Need time-ordered IDs?
  ↓ Yes → Use ULID (better than v1)
  ↓ No
→ Use v4 (default choice)

Tools: Compare UUID versions with our UUID Generator which supports all versions.

UUID vs GUID vs Auto-Increment IDs

Choosing the right ID strategy impacts performance, scalability, and security. Here’s the real comparison.

UUID vs Auto-Increment: The Trade-offs

Auto-increment IDs:

CREATE TABLE users (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100)
);

INSERT INTO users (name) VALUES ('John');  -- Gets id = 1
INSERT INTO users (name) VALUES ('Jane');  -- Gets id = 2

UUID IDs:

CREATE TABLE users (
  id BINARY(16) PRIMARY KEY,
  name VARCHAR(100)
);

INSERT INTO users VALUES (UUID_TO_BIN('550e8400-e29b-41d4-a716-446655440000'), 'John');
INSERT INTO users VALUES (UUID_TO_BIN('7c9e6679-7425-40de-944b-e07fc1f90ae7'), 'Jane');

Complete Comparison Table

Feature Auto-Increment UUID
Generation Database-dependent Application-generated
Uniqueness Per table Globally unique
Size 4-8 bytes 16 bytes
Predictability Fully predictable Unpredictable
Distributed systems Requires coordination No coordination
Merge databases ID conflicts No conflicts
Index performance Excellent (sequential) Good (with optimization)
URL length Short /users/12345 Long /users/550e8400...
Privacy ❌ Exposes count ✅ Hides count
Offline generation ❌ Needs database ✅ Works offline

When Auto-Increment Wins

Use auto-increment when:

  1. Single database - No distribution needed
  2. Public enumeration is OK - Don’t care if users can see record count
  3. Space matters - 8 bytes vs 16 bytes
  4. Join performance critical - Smaller indexes = faster joins
  5. Human-readable IDs needed - “Ticket #12345”

Example:

-- Internal audit logs
CREATE TABLE audit_logs (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  event_type VARCHAR(50),
  created_at TIMESTAMP
);
-- Perfect: Sequential, fast, no privacy concerns

When UUIDs Win

Use UUIDs when:

  1. Distributed systems - Multiple databases/services
  2. Microservices - Each service generates IDs independently
  3. Privacy matters - Don’t want to expose record counts
  4. Offline-first - Mobile apps that sync later
  5. Merging data - Combining data from multiple sources
  6. Sharding - Distributing data across multiple databases

Example:

// Microservices architecture
// Order service generates UUID
const orderId = crypto.randomUUID();
await orderService.create({id: orderId, ...});

// Payment service uses same UUID (no coordination needed)
await paymentService.create({orderId: orderId, ...});

// Shipping service uses same UUID
await shippingService.create({orderId: orderId, ...});

Hybrid Approach: Best of Both Worlds

Use auto-increment internally, UUID externally:

CREATE TABLE users (
  -- Internal: Fast joins, compact
  internal_id BIGINT AUTO_INCREMENT PRIMARY KEY,

  -- External: Public-facing, secure
  public_id BINARY(16) UNIQUE NOT NULL,

  name VARCHAR(100),

  INDEX idx_public_id (public_id)
);

Usage:

// Public API uses UUID
GET /api/users/550e8400-e29b-41d4-a716-446655440000

// Internal queries use auto-increment
SELECT * FROM users u
JOIN orders o ON o.user_internal_id = u.internal_id  -- Fast!
WHERE u.public_id = UUID_TO_BIN('550e8400...');

Benefits:

  • ✅ Fast internal joins (integer keys)
  • ✅ Secure public URLs (UUIDs)
  • ✅ Compact storage for relationships
  • ✅ Privacy protection

Real-World Examples

E-commerce platform:

-- Products: Public enumeration OK, SEO-friendly IDs
CREATE TABLE products (
  id INT AUTO_INCREMENT PRIMARY KEY,  -- /products/12345
  sku VARCHAR(50),
  name VARCHAR(200)
);

-- Orders: Privacy required, distributed processing
CREATE TABLE orders (
  id BINARY(16) PRIMARY KEY,  -- UUID
  customer_id BINARY(16),
  status VARCHAR(20)
);

-- Order items: Internal only
CREATE TABLE order_items (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,  -- Fast joins
  order_id BINARY(16),
  product_id INT
);

SaaS application:

-- Organizations: Distributed, multi-tenant
CREATE TABLE organizations (
  id BINARY(16) PRIMARY KEY,  -- UUID
  name VARCHAR(100)
);

-- Users: Privacy required
CREATE TABLE users (
  id BINARY(16) PRIMARY KEY,  -- UUID
  organization_id BINARY(16),
  email VARCHAR(255)
);

-- Activity logs: Internal analytics
CREATE TABLE activity_logs (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,  -- Auto-increment
  user_id BINARY(16),
  action VARCHAR(50),
  created_at TIMESTAMP
);

The Enumeration Attack

Why auto-increment can be dangerous:

# Attacker discovers user ID pattern
curl https://api.example.com/users/1000
curl https://api.example.com/users/1001
curl https://api.example.com/users/1002
# ... iterate through all users

# Reveals:
# - Total number of users (business intelligence)
# - User creation rate (growth metrics)
# - Individual user data (if auth is broken)

UUID prevents this:

# Attacker knows one UUID
curl https://api.example.com/users/550e8400-e29b-41d4-a716-446655440000

# Cannot guess next UUID (2^122 possibilities)
curl https://api.example.com/users/550e8401-e29b-41d4-a716-446655440000
# ❌ 404 Not Found

Tools: Generate secure UUIDs with our UUID Generator and create random strings with our Random String Generator.

How to Generate UUIDs (All Languages)

Here’s production-ready code for generating UUIDs in every major language.

JavaScript / TypeScript

Modern browsers & Node.js 15+:

// Built-in crypto.randomUUID() - UUID v4
const uuid = crypto.randomUUID();
console.log(uuid);
// '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'

// No dependencies needed!

Node.js with uuid library (all versions):

npm install uuid
const { v1: uuidv1, v4: uuidv4, v5: uuidv5 } = require('uuid');

// Version 4 (random) - most common
const id = uuidv4();
// '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'

// Version 1 (timestamp + MAC)
const idV1 = uuidv1();
// '6ba7b810-9dad-11d1-80b4-00c04fd430c8'

// Version 5 (deterministic hash)
const DNS_NAMESPACE = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
const idV5 = uuidv5('example.com', DNS_NAMESPACE);
// '9073926b-929f-31c2-abc9-fad77ae3e8eb'

TypeScript with type safety:

import { v4 as uuidv4 } from 'uuid';

type UUID = string & { __brand: 'UUID' };

function generateUUID(): UUID {
  return uuidv4() as UUID;
}

function isValidUUID(value: string): value is UUID {
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
  return uuidRegex.test(value);
}

// Usage
const userId: UUID = generateUUID();

React example:

import { useState } from 'react';

function UserForm() {
  const [userId] = useState(() => crypto.randomUUID());

  const handleSubmit = async (e) => {
    e.preventDefault();

    await fetch('/api/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        id: userId,  // UUID generated on client
        name: e.target.name.value
      })
    });
  };

  return <form onSubmit={handleSubmit}>...</form>;
}

Python

Built-in uuid module:

import uuid

# Version 4 (random) - most common
id = uuid.uuid4()
print(id)
# UUID('9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d')

# Get as string
id_str = str(uuid.uuid4())
# '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'

# Version 1 (timestamp)
id_v1 = uuid.uuid1()

# Version 5 (hash-based)
namespace = uuid.NAMESPACE_DNS
id_v5 = uuid.uuid5(namespace, 'example.com')

Django model:

from django.db import models
import uuid

class User(models.Model):
    id = models.UUIDField(
        primary_key=True,
        default=uuid.uuid4,
        editable=False
    )
    name = models.CharField(max_length=100)
    email = models.EmailField(unique=True)
    created_at = models.DateTimeField(auto_now_add=True)

FastAPI example:

from fastapi import FastAPI
from pydantic import BaseModel, UUID4
import uuid

app = FastAPI()

class User(BaseModel):
    id: UUID4
    name: str
    email: str

@app.post("/users")
async def create_user(name: str, email: str):
    user = User(
        id=uuid.uuid4(),
        name=name,
        email=email
    )
    # Save to database...
    return user

Java

Built-in java.util.UUID:

import java.util.UUID;

public class UUIDExample {
    public static void main(String[] args) {
        // Version 4 (random)
        UUID uuid = UUID.randomUUID();
        System.out.println(uuid.toString());
        // 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d

        // Parse from string
        UUID parsed = UUID.fromString("550e8400-e29b-41d4-a716-446655440000");

        // Version 3 (MD5 hash)
        UUID nameBasedUUID = UUID.nameUUIDFromBytes("example.com".getBytes());
    }
}

Spring Boot entity:

import jakarta.persistence.*;
import java.util.UUID;

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(columnDefinition = "BINARY(16)")
    private UUID id;

    private String name;
    private String email;

    public User() {
        this.id = UUID.randomUUID();
    }

    // Getters and setters...
}

C# / .NET

Built-in Guid class:

using System;

class Program
{
    static void Main()
    {
        // Generate new GUID (UUID v4)
        Guid guid = Guid.NewGuid();
        Console.WriteLine(guid.ToString());
        // 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d

        // Different formats
        Console.WriteLine(guid.ToString("N")); // No hyphens
        Console.WriteLine(guid.ToString("D")); // Default (with hyphens)
        Console.WriteLine(guid.ToString("B")); // With braces
        Console.WriteLine(guid.ToString("P")); // With parentheses

        // Parse from string
        Guid parsed = Guid.Parse("550e8400-e29b-41d4-a716-446655440000");
    }
}

Entity Framework Core:

using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;

public class User
{
    [Key]
    public Guid Id { get; set; } = Guid.NewGuid();

    [Required]
    [MaxLength(100)]
    public string Name { get; set; }

    [Required]
    [EmailAddress]
    public string Email { get; set; }
}

public class AppDbContext : DbContext
{
    public DbSet<User> Users { get; set; }
}

Go

google/uuid package:

go get github.com/google/uuid
package main

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

func main() {
    // Version 4 (random)
    id := uuid.New()
    fmt.Println(id.String())
    // 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d

    // Parse from string
    parsed, err := uuid.Parse("550e8400-e29b-41d4-a716-446655440000")
    if err != nil {
        panic(err)
    }

    // Nil UUID (all zeros)
    nilUUID := uuid.Nil
    fmt.Println(nilUUID) // 00000000-0000-0000-0000-000000000000
}

GORM model:

import (
    "github.com/google/uuid"
    "gorm.io/gorm"
)

type User struct {
    ID    uuid.UUID `gorm:"type:uuid;primary_key;"`
    Name  string    `gorm:"size:100;not null"`
    Email string    `gorm:"size:255;unique;not null"`
}

func (u *User) BeforeCreate(tx *gorm.DB) error {
    if u.ID == uuid.Nil {
        u.ID = uuid.New()
    }
    return nil
}

PHP

Built-in (PHP 8.3+):

<?php
// PHP 8.3+ has native UUID support
$uuid = Uuid::v4();
echo $uuid;
// 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d

ramsey/uuid package (all PHP versions):

composer require ramsey/uuid
<?php
use Ramsey\Uuid\Uuid;

// Version 4 (random)
$uuid = Uuid::uuid4();
echo $uuid->toString();
// 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d

// Version 1 (timestamp)
$uuid1 = Uuid::uuid1();

// Version 5 (hash-based)
$uuid5 = Uuid::uuid5(Uuid::NAMESPACE_DNS, 'example.com');

// Parse from string
$parsed = Uuid::fromString('550e8400-e29b-41d4-a716-446655440000');

Laravel model:

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;

class User extends Model
{
    protected $keyType = 'string';
    public $incrementing = false;

    protected $fillable = ['name', 'email'];

    protected static function boot()
    {
        parent::boot();

        static::creating(function ($model) {
            if (empty($model->id)) {
                $model->id = (string) Str::uuid();
            }
        });
    }
}

Ruby

Built-in SecureRandom:

require 'securerandom'

# Generate UUID v4
uuid = SecureRandom.uuid
puts uuid
# 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d

Rails migration:

class CreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users, id: :uuid do |t|
      t.string :name, null: false
      t.string :email, null: false, index: { unique: true }

      t.timestamps
    end
  end
end

Rails model:

class User < ApplicationRecord
  before_create :generate_uuid

  private

  def generate_uuid
    self.id = SecureRandom.uuid if id.blank?
  end
end

Rust

uuid crate:

cargo add uuid --features v4
use uuid::Uuid;

fn main() {
    // Version 4 (random)
    let id = Uuid::new_v4();
    println!("{}", id);
    // 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d

    // Parse from string
    let parsed = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();

    // Get as bytes
    let bytes = id.as_bytes();
}

Online Tool (No Code Required)

Don’t want to write code? Use our UUID Generator:

  • Generate multiple UUIDs instantly
  • Choose version (v1, v4, v5)
  • Validate existing UUIDs
  • Copy to clipboard
  • Bulk generation

Tools: Generate UUIDs with our UUID Generator and create unique tokens with our Random String Generator.

Using UUIDs in Databases (Performance Optimization)

Storing UUIDs wrong can destroy your database performance. Here’s how to do it right.

MySQL / MariaDB

❌ Wrong way (VARCHAR):

CREATE TABLE users (
  id VARCHAR(36) PRIMARY KEY,  -- 36 bytes + overhead
  name VARCHAR(100)
);

-- Slow inserts, slow lookups, huge indexes

✅ Right way (BINARY):

CREATE TABLE users (
  id BINARY(16) PRIMARY KEY,  -- 16 bytes, compact
  name VARCHAR(100),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

  INDEX idx_created (created_at)
) ENGINE=InnoDB;

-- Insert
INSERT INTO users (id, name) VALUES
  (UUID_TO_BIN('550e8400-e29b-41d4-a716-446655440000'), 'John');

-- Query
SELECT BIN_TO_UUID(id), name
FROM users
WHERE id = UUID_TO_BIN('550e8400-e29b-41d4-a716-446655440000');

Ordered UUIDs for better insert performance:

-- MySQL 8.0+ supports ordered UUIDs
INSERT INTO users (id, name) VALUES
  (UUID_TO_BIN(UUID(), 1), 'John');  -- 1 = swap time fields for ordering

Stored functions for convenience:

DELIMITER $$

CREATE FUNCTION BIN_UUID() RETURNS BINARY(16)
DETERMINISTIC
BEGIN
  RETURN UUID_TO_BIN(UUID(), 1);
END$$

DELIMITER ;

-- Usage
INSERT INTO users (id, name) VALUES (BIN_UUID(), 'John');

PostgreSQL

Use UUID extension:

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  name VARCHAR(100) NOT NULL,
  email VARCHAR(255) UNIQUE NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Insert with explicit UUID
INSERT INTO users (id, name, email) VALUES
  ('550e8400-e29b-41d4-a716-446655440000', 'John', 'john@example.com');

-- Insert with auto-generated UUID
INSERT INTO users (name, email) VALUES
  ('Jane', 'jane@example.com');

Modern PostgreSQL (14+) built-in:

CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name VARCHAR(100) NOT NULL
);

Performance optimization with btree_gist:

CREATE EXTENSION btree_gist;

CREATE INDEX idx_users_id ON users USING GIST (id);

MongoDB

Native UUID support:

const { MongoClient, UUID } = require('mongodb');

// Generate UUID
const userId = new UUID();

// Insert document
await collection.insertOne({
  _id: userId,  // MongoDB stores as BinData
  name: 'John',
  email: 'john@example.com'
});

// Query
const user = await collection.findOne({
  _id: new UUID('550e8400-e29b-41d4-a716-446655440000')
});

Mongoose schema:

const mongoose = require('mongoose');
const { v4: uuidv4 } = require('uuid');

const userSchema = new mongoose.Schema({
  _id: {
    type: String,
    default: uuidv4
  },
  name: {
    type: String,
    required: true
  },
  email: {
    type: String,
    required: true,
    unique: true
  }
}, { _id: false });  // Disable auto _id

const User = mongoose.model('User', userSchema);

SQLite

Store as BLOB:

CREATE TABLE users (
  id BLOB PRIMARY KEY,  -- 16 bytes
  name TEXT NOT NULL,
  email TEXT UNIQUE NOT NULL
);

-- Python example
import uuid
import sqlite3

conn = sqlite3.connect('database.db')
cursor = conn.cursor()

# Insert
user_id = uuid.uuid4()
cursor.execute(
    'INSERT INTO users (id, name, email) VALUES (?, ?, ?)',
    (user_id.bytes, 'John', 'john@example.com')
)

# Query
cursor.execute('SELECT id, name FROM users WHERE id = ?', (user_id.bytes,))
row = cursor.fetchone()
retrieved_id = uuid.UUID(bytes=row[0])

Storage Comparison

Storage Type Size Query Speed Index Size Use When
VARCHAR(36) 36+ bytes Slow Large Never (unless legacy)
CHAR(36) 36 bytes Slow Large Never
BINARY(16) 16 bytes Fast Compact MySQL best practice
UUID type 16 bytes Fast Compact PostgreSQL best practice
String 36 bytes Medium Large MongoDB (if not using UUID type)

Storage savings example:

1 million users with VARCHAR(36):
  36MB just for IDs
  + index overhead ≈ 100MB total

1 million users with BINARY(16):
  16MB just for IDs
  + index overhead ≈ 45MB total

Savings: 55MB (55% reduction)

Indexing Best Practices

Primary key index:

-- ✅ Good: UUID as primary key
CREATE TABLE users (
  id BINARY(16) PRIMARY KEY,
  email VARCHAR(255) UNIQUE
);

-- ⚠️ Consider: Composite key for foreign relationships
CREATE TABLE orders (
  id BINARY(16) PRIMARY KEY,
  user_id BINARY(16) NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

  INDEX idx_user_created (user_id, created_at)
);

Foreign key indexes:

CREATE TABLE posts (
  id BINARY(16) PRIMARY KEY,
  user_id BINARY(16) NOT NULL,
  title VARCHAR(200),

  INDEX idx_user (user_id),  -- Essential for joins
  FOREIGN KEY (user_id) REFERENCES users(id)
);

Covering indexes:

-- Optimize common queries
CREATE INDEX idx_user_email_name
ON users(email, name);

-- This query uses index only (no table access)
SELECT name FROM users WHERE email = 'john@example.com';

Performance Tips

1. Use BINARY(16) in MySQL, not VARCHAR:

-- ❌ Slow
id VARCHAR(36)  -- 36 bytes, string comparison

-- ✅ Fast
id BINARY(16)   -- 16 bytes, binary comparison

2. Consider ordered UUIDs for better insert performance:

-- MySQL 8.0+
UUID_TO_BIN(UUID(), 1)  -- Ordered by timestamp

-- Reduces page splits in B-tree index

3. Partition large tables:

CREATE TABLE events (
  id BINARY(16),
  user_id BINARY(16),
  created_at TIMESTAMP,
  data JSON
) PARTITION BY RANGE (UNIX_TIMESTAMP(created_at)) (
  PARTITION p2024 VALUES LESS THAN (UNIX_TIMESTAMP('2025-01-01')),
  PARTITION p2025 VALUES LESS THAN (UNIX_TIMESTAMP('2026-01-01'))
);

4. Use appropriate index types:

-- PostgreSQL: Use btree (default)
CREATE INDEX idx_users_id ON users USING btree (id);

-- For range queries on UUIDs (rare)
CREATE INDEX idx_users_created ON users USING brin (created_at);

Tools: Test UUID storage with our SQL Formatter and optimize queries with performance analysis.

UUID Validation (Checking Format)

Validating UUIDs prevents bugs and security issues. Here’s how to do it right.

Regex Pattern for UUID Validation

Standard UUID v4 regex:

const UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

function isValidUUIDv4(uuid) {
  return UUID_V4_REGEX.test(uuid);
}

// Test
console.log(isValidUUIDv4('550e8400-e29b-41d4-a716-446655440000')); // true
console.log(isValidUUIDv4('not-a-uuid')); // false
console.log(isValidUUIDv4('550e8400-e29b-41d4-a716-44665544000')); // false (too short)

Any UUID version (v1-v5):

const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

function isValidUUID(uuid) {
  return UUID_REGEX.test(uuid);
}

Breaking down the regex:

^                             Start of string
[0-9a-f]{8}                   8 hex digits (time-low)
-                             Hyphen
[0-9a-f]{4}                   4 hex digits (time-mid)
-                             Hyphen
4                             Version 4 marker
[0-9a-f]{3}                   3 hex digits
-                             Hyphen
[89ab]                        Variant bits (10xx)
[0-9a-f]{3}                   3 hex digits
-                             Hyphen
[0-9a-f]{12}                  12 hex digits (node)
$                             End of string
i                             Case-insensitive

JavaScript Validation

Quick validation:

function isValidUUID(uuid) {
  if (typeof uuid !== 'string') return false;

  return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(uuid);
}

With version detection:

function validateUUID(uuid) {
  if (typeof uuid !== 'string') {
    return { valid: false, error: 'UUID must be a string' };
  }

  const match = uuid.match(/^[0-9a-f]{8}-[0-9a-f]{4}-([1-5])[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);

  if (!match) {
    return { valid: false, error: 'Invalid UUID format' };
  }

  return {
    valid: true,
    version: parseInt(match[1]),
    uuid: uuid.toLowerCase()
  };
}

// Usage
const result = validateUUID('550e8400-e29b-41d4-a716-446655440000');
console.log(result);
// { valid: true, version: 4, uuid: '550e8400-e29b-41d4-a716-446655440000' }

TypeScript type guard:

type UUID = string & { __brand: 'UUID' };

function isUUID(value: unknown): value is UUID {
  if (typeof value !== 'string') return false;
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
}

function processUser(id: UUID) {
  // id is guaranteed to be a valid UUID here
  console.log(`Processing user ${id}`);
}

const userId = '550e8400-e29b-41d4-a716-446655440000';
if (isUUID(userId)) {
  processUser(userId);  // TypeScript knows this is safe
}

Python Validation

Using uuid module:

import uuid

def is_valid_uuid(value):
    try:
        uuid.UUID(value)
        return True
    except (ValueError, AttributeError):
        return False

# Test
print(is_valid_uuid('550e8400-e29b-41d4-a716-446655440000'))  # True
print(is_valid_uuid('not-a-uuid'))  # False

With version validation:

def validate_uuid(value, version=None):
    try:
        uuid_obj = uuid.UUID(value)

        if version and uuid_obj.version != version:
            return False, f'Expected UUID v{version}, got v{uuid_obj.version}'

        return True, uuid_obj
    except ValueError as e:
        return False, str(e)

# Usage
valid, result = validate_uuid('550e8400-e29b-41d4-a716-446655440000', version=4)
if valid:
    print(f'Valid UUID v{result.version}')
else:
    print(f'Invalid: {result}')

API Input Validation

Express.js middleware:

const { validate: isUUID } = require('uuid');

function validateUUIDParam(paramName) {
  return (req, res, next) => {
    const uuid = req.params[paramName];

    if (!isUUID(uuid)) {
      return res.status(400).json({
        error: 'Invalid UUID',
        message: `Parameter '${paramName}' must be a valid UUID`
      });
    }

    next();
  };
}

// Usage
app.get('/users/:id', validateUUIDParam('id'), (req, res) => {
  // req.params.id is guaranteed to be valid UUID
  const userId = req.params.id;
  // ... fetch user
});

FastAPI validation:

from fastapi import FastAPI, HTTPException, Path
from pydantic import UUID4
import uuid

app = FastAPI()

@app.get("/users/{user_id}")
async def get_user(user_id: UUID4):
    # user_id is automatically validated and converted
    print(f"Fetching user {user_id}")
    # ... fetch from database
    return {"id": str(user_id)}

# Manual validation if needed
@app.get("/custom/{id}")
async def custom_endpoint(id: str):
    try:
        user_id = uuid.UUID(id)
    except ValueError:
        raise HTTPException(status_code=400, detail="Invalid UUID")

    return {"id": str(user_id)}

Django validator:

from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
import uuid

uuid_validator = RegexValidator(
    regex=r'^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$',
    message='Invalid UUID format',
    flags=re.IGNORECASE
)

# Or use built-in
from django.db import models

class User(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4)
    # Django automatically validates UUID format

Database Validation

MySQL check constraint:

CREATE TABLE users (
  id BINARY(16) PRIMARY KEY,
  public_id CHAR(36) CHECK (
    public_id REGEXP '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
  ),
  name VARCHAR(100)
);

PostgreSQL check constraint:

CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  external_id TEXT CHECK (
    external_id ~ '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
  ),
  name VARCHAR(100)
);

Common Validation Mistakes

❌ Wrong: Accepting any string:

function getUser(id) {
  // No validation - SQL injection risk!
  return db.query(`SELECT * FROM users WHERE id = '${id}'`);
}

getUser("' OR '1'='1"); // 💀 SQL injection

✅ Right: Validate before using:

function getUser(id) {
  if (!isValidUUID(id)) {
    throw new Error('Invalid user ID format');
  }

  return db.query('SELECT * FROM users WHERE id = ?', [id]);
}

❌ Wrong: Trusting client input:

app.post('/users', async (req, res) => {
  // Client sends ID - might be invalid or duplicate!
  const user = await db.insert({
    id: req.body.id,  // Dangerous!
    name: req.body.name
  });
});

✅ Right: Generate server-side:

app.post('/users', async (req, res) => {
  const user = await db.insert({
    id: crypto.randomUUID(),  // Server generates
    name: req.body.name
  });
  res.json(user);
});

Tools: Validate UUIDs instantly with our UUID Generator and test regex patterns with our Regex Tester.

Common UUID Use Cases

UUIDs shine in specific scenarios. Here’s when and how to use them.

Database Primary Keys

Why UUIDs make great primary keys:

-- Traditional auto-increment problem
CREATE TABLE orders (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT,
  status VARCHAR(20)
);

-- Problem: Need to insert, then get ID back
INSERT INTO orders (user_id, status) VALUES (123, 'pending');
SELECT LAST_INSERT_ID();  -- Now we know the ID

-- Problem: Can't create order_items until order is inserted

UUID solution:

CREATE TABLE orders (
  id BINARY(16) PRIMARY KEY,
  user_id BINARY(16),
  status VARCHAR(20)
);

-- Know the ID before inserting!
SET @order_id = UUID_TO_BIN(UUID());

INSERT INTO orders VALUES (@order_id, UUID_TO_BIN('...'), 'pending');

-- Can immediately insert related records
INSERT INTO order_items VALUES
  (UUID_TO_BIN(UUID()), @order_id, 'Product A'),
  (UUID_TO_BIN(UUID()), @order_id, 'Product B');

Real-world e-commerce example:

async function createOrder(userId, items) {
  const orderId = crypto.randomUUID();
  const orderItemIds = items.map(() => crypto.randomUUID());

  // Create everything in parallel - we know all IDs upfront!
  await Promise.all([
    db.orders.insert({ id: orderId, userId, status: 'pending' }),

    db.orderItems.insertMany(
      items.map((item, i) => ({
        id: orderItemIds[i],
        orderId: orderId,
        productId: item.productId,
        quantity: item.quantity
      }))
    ),

    paymentService.initiate({ orderId, amount: total }),

    inventoryService.reserve({ orderId, items }),

    emailService.send({ userId, orderId, subject: 'Order Confirmation' })
  ]);

  return { orderId, status: 'created' };
}

API Request IDs (Tracing)

Track requests across microservices:

const express = require('express');
const { v4: uuidv4 } = require('uuid');

// Middleware to add request ID
app.use((req, res, next) => {
  req.id = req.headers['x-request-id'] || uuidv4();
  res.setHeader('x-request-id', req.id);
  next();
});

// Logger includes request ID
const logger = require('pino')();

app.get('/api/users/:id', async (req, res) => {
  logger.info({ requestId: req.id }, 'Fetching user');

  try {
    const user = await userService.get(req.params.id, { requestId: req.id });
    res.json(user);
  } catch (error) {
    logger.error({ requestId: req.id, error }, 'Failed to fetch user');
    res.status(500).json({ error: 'Internal server error', requestId: req.id });
  }
});

Distributed tracing:

// Service A (API Gateway)
async function handleRequest(req) {
  const traceId = uuidv4();

  logger.info({ traceId }, 'Request received');

  // Call Service B
  const userData = await fetch('http://service-b/users/123', {
    headers: { 'x-trace-id': traceId }
  });

  // Call Service C
  const orderData = await fetch('http://service-c/orders', {
    headers: { 'x-trace-id': traceId }
  });

  return { user: userData, orders: orderData };
}

// Service B logs:
// { traceId: '550e8400-...', service: 'user-service', message: 'Fetched user' }

// Service C logs:
// { traceId: '550e8400-...', service: 'order-service', message: 'Fetched orders' }

// Now you can grep logs by traceId across all services!

Session Identifiers

Secure session management:

const sessions = new Map();

function createSession(userId) {
  const sessionId = crypto.randomUUID();

  sessions.set(sessionId, {
    userId,
    createdAt: Date.now(),
    expiresAt: Date.now() + (24 * 60 * 60 * 1000)  // 24 hours
  });

  return sessionId;
}

// Login endpoint
app.post('/login', async (req, res) => {
  const user = await authenticate(req.body.email, req.body.password);

  if (!user) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  const sessionId = createSession(user.id);

  res.cookie('session', sessionId, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 24 * 60 * 60 * 1000
  });

  res.json({ message: 'Logged in successfully' });
});

File Naming (Unique Uploads)

Prevent filename collisions:

const multer = require('multer');
const { v4: uuidv4 } = require('uuid');
const path = require('path');

const storage = multer.diskStorage({
  destination: 'uploads/',
  filename: (req, file, cb) => {
    const fileExt = path.extname(file.originalname);
    const uuid = uuidv4();

    // Original: photo.jpg
    // Saved as: 550e8400-e29b-41d4-a716-446655440000.jpg
    cb(null, `${uuid}${fileExt}`);
  }
});

const upload = multer({ storage });

app.post('/upload', upload.single('file'), (req, res) => {
  res.json({
    filename: req.file.filename,
    url: `/uploads/${req.file.filename}`
  });
});

S3 object keys:

const AWS = require('aws-sdk');
const s3 = new AWS.S3();

async function uploadFile(file) {
  const fileId = uuidv4();
  const key = `uploads/${fileId}/${file.originalname}`;

  await s3.putObject({
    Bucket: 'my-bucket',
    Key: key,
    Body: file.buffer,
    ContentType: file.mimetype
  }).promise();

  return {
    id: fileId,
    url: `https://my-bucket.s3.amazonaws.com/${key}`
  };
}

Idempotency Keys (Preventing Duplicates)

Prevent duplicate payments:

const processedRequests = new Map();

app.post('/api/payments', async (req, res) => {
  // Client sends idempotency key
  const idempotencyKey = req.headers['idempotency-key'];

  if (!idempotencyKey || !isValidUUID(idempotencyKey)) {
    return res.status(400).json({
      error: 'Missing or invalid idempotency-key header'
    });
  }

  // Check if we've processed this request before
  if (processedRequests.has(idempotencyKey)) {
    const previousResponse = processedRequests.get(idempotencyKey);
    return res.json(previousResponse);  // Return cached response
  }

  // Process payment
  const payment = await processPayment(req.body);

  // Cache the response
  processedRequests.set(idempotencyKey, payment);

  res.json(payment);
});

Client usage:

async function makePayment(paymentData) {
  const idempotencyKey = crypto.randomUUID();

  // If this request fails and is retried, same idempotency key ensures
  // we don't charge the customer twice
  const response = await fetch('/api/payments', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Idempotency-Key': idempotencyKey
    },
    body: JSON.stringify(paymentData)
  });

  return response.json();
}

Message Queue Deduplication

Prevent duplicate message processing:

const processed = new Set();

async function handleMessage(message) {
  // Each message has UUID
  if (processed.has(message.id)) {
    console.log('Duplicate message, skipping');
    return;
  }

  try {
    await processMessage(message.data);
    processed.add(message.id);
  } catch (error) {
    console.error('Failed to process message', error);
    // Don't add to processed set - allow retry
  }
}

// Redis-based deduplication
const Redis = require('ioredis');
const redis = new Redis();

async function handleMessageWithRedis(message) {
  const key = `processed:${message.id}`;

  // Check if already processed
  const exists = await redis.exists(key);
  if (exists) {
    return;
  }

  // Process message
  await processMessage(message.data);

  // Mark as processed (with TTL)
  await redis.setex(key, 86400, '1');  // 24 hours
}

Tools: Generate request IDs with our UUID Generator and create secure tokens with our Random String Generator.

UUID Alternatives: ULID, NanoID, and When to Use Them

UUIDs aren’t always the best choice. Here are modern alternatives and when to use them.

ULID (Universally Unique Lexicographically Sortable Identifier)

What makes ULID better:

UUID v4:  9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d  (Random, not sortable)
ULID:     01ARZ3NDEKTSV4RRFFQ69G5FAV            (Sortable by time!)

ULID structure:

 01ARZ3NDEKTSV4RRFFQ69G5FAV
 |----------| |----------|
  Timestamp      Random
  (48 bits)    (80 bits)

  Timestamp: Millisecond precision
  Random: Cryptographically strong
  Total: 128 bits (same as UUID)

Key advantages over UUID:

  • ✅ Sortable by creation time
  • ✅ URL-safe (no hyphens)
  • ✅ Case-insensitive (base32 encoded)
  • ✅ 26 characters (vs UUID’s 36)
  • ✅ Can extract timestamp

JavaScript usage:

npm install ulid
const { ulid } = require('ulid');

// Generate ULID
const id = ulid();
// 01ARZ3NDEKTSV4RRFFQ69G5FAV

// Generate with specific timestamp
const id2 = ulid(1609459200000);  // Jan 1, 2021

// Decode timestamp
const { decodeTime } = require('ulid');
const timestamp = decodeTime(id);
console.log(new Date(timestamp));

Database benefits:

-- ULIDs are naturally ordered
CREATE TABLE events (
  id CHAR(26) PRIMARY KEY,  -- ULID
  data JSON,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Inserting ULIDs in chronological order improves B-tree performance
INSERT INTO events VALUES (ulid(), '{"event": "click"}');
INSERT INTO events VALUES (ulid(), '{"event": "view"}');

-- Range queries are efficient
SELECT * FROM events
WHERE id >= '01ARZ3NDEK'  -- All events after this time
  AND id < '01ARZ3NDEL';

When to use ULID:

  • 🟢 Time-series data (logs, events, analytics)
  • 🟢 When you need sortable IDs
  • 🟢 Database insert performance matters
  • 🟢 Want shorter IDs than UUID
  • 🔴 Not when timestamp leakage is a concern

NanoID (Short, URL-Safe IDs)

What makes NanoID different:

UUID:     550e8400-e29b-41d4-a716-446655440000  (36 chars)
NanoID:   V1StGXR8_Z5jdHi6B-myT                 (21 chars)

Key features:

  • Compact - Default 21 characters
  • URL-safe - No special characters
  • Fast - 2x faster than UUID
  • Customizable - Adjust length and alphabet
  • Collision-resistant - Need to generate 1M IDs/hour for ~36 years to have 1% collision chance

JavaScript usage:

npm install nanoid
const { nanoid } = require('nanoid');

// Default: 21 characters
const id = nanoid();
// V1StGXR8_Z5jdHi6B-myT

// Custom length
const shortId = nanoid(10);
// IRFa-VaY2b

// Custom alphabet
const { customAlphabet } = require('nanoid');
const numbersOnly = customAlphabet('0123456789', 10);
const numericId = numbersOnly();
// 4193189385

Use in URLs:

// Clean, short URLs
app.get('/share/:id', async (req, res) => {
  const share = await db.shares.findOne({ id: req.params.id });
  res.send(share.content);
});

// Create shareable link
app.post('/share', async (req, res) => {
  const id = nanoid(8);  // Even shorter for shares

  await db.shares.insert({
    id,
    content: req.body.content,
    expiresAt: Date.now() + 86400000
  });

  res.json({ url: `https://example.com/share/${id}` });
});

// Result: https://example.com/share/iR5a-VaY
// vs UUID: https://example.com/share/550e8400-e29b-41d4-a716-446655440000

When to use NanoID:

  • 🟢 Short URLs and links
  • 🟢 URL slugs
  • 🟢 Temporary tokens
  • 🟢 Public-facing IDs where length matters
  • 🟢 File naming
  • 🔴 Not for distributed systems requiring global uniqueness guarantees

Snowflake IDs (Twitter’s Approach)

How Snowflake IDs work:

 01001010110101... (64 bits total)
 |--------||--||-----------|
 Timestamp Worker  Sequence
 (41 bits)(10)(  12 bits  )

 Timestamp: Milliseconds since custom epoch
 Worker: Unique machine ID (1024 workers max)
 Sequence: Per-worker counter (4096/ms)

Benefits:

  • ✅ Sortable by time
  • ✅ Distributed generation
  • ✅ No coordination needed
  • ✅ Fits in 64-bit integer
  • ✅ High throughput (4M+ IDs/sec per worker)

Node.js implementation:

npm install snowflake-id
const Snowflake = require('snowflake-id');

const snowflake = new Snowflake({
  worker_id: 1,  // Unique for each service instance
  datacenter_id: 1
});

const id = snowflake.generate();
// 175928847299117063n (BigInt)

// Use as string
const idStr = id.toString();
// '175928847299117063'

When to use Snowflake:

  • 🟢 High-throughput systems (Twitter-scale)
  • 🟢 Need sortable 64-bit integers
  • 🟢 Distributed ID generation
  • 🟢 Real-time analytics
  • 🔴 Not if you need more than 1024 workers

Comparison Table

Type Length Sortable Collision Risk Speed Use Case
UUID v4 36 chars Extremely low Medium General purpose
ULID 26 chars Extremely low Fast Time-series
NanoID 21 chars Very low Very fast Short URLs
Snowflake 19 digits None (sequential) Very fast High-throughput
Auto-increment Variable None (sequential) Fastest Single database

Decision Matrix

Choose UUID v4 when:

  • General-purpose unique IDs needed
  • Distribution across systems
  • No timestamp leakage acceptable

Choose ULID when:

  • Need sortable IDs
  • Time-series data
  • Database insert performance critical
  • Timestamp in ID is useful

Choose NanoID when:

  • Short URLs required
  • Public-facing IDs
  • Speed critical
  • Length matters more than global uniqueness

Choose Snowflake when:

  • Extreme throughput needed
  • Twitter/Instagram scale
  • 64-bit integers preferred
  • Have <1024 workers

Mixed approach:

// Internal DB: Auto-increment for performance
// External API: UUID for security
// URLs: NanoID for brevity
// Events: ULID for sorting

class User {
  internalId: number;      // Auto-increment
  publicId: string;        // UUID
  shareLink: string;       // NanoID
}

Tools: Generate ULIDs and NanoIDs with our UUID Generator and create custom IDs with our Random String Generator.

UUID Best Practices

Follow these patterns to avoid common pitfalls and optimize UUID usage.

Use v4 for Most Cases

Default choice:

// ✅ Right: UUID v4 for general use
const userId = crypto.randomUUID();  // v4

// ❌ Wrong: UUID v1 leaks MAC address
const { v1 } = require('uuid');
const userId = v1();  // Contains MAC address and timestamp

Exception: Use v5 for deterministic IDs:

const { v5 } = require('uuid');

// Generate consistent ID for external resource
function getCacheKey(url) {
  const namespace = '6ba7b814-9dad-11d1-80b4-00c04fd430c8';
  return v5(url, namespace);  // Always same ID for same URL
}

const key1 = getCacheKey('https://example.com');
const key2 = getCacheKey('https://example.com');
// key1 === key2 ✅

Store as Binary in Databases

MySQL optimization:

-- ❌ Slow: VARCHAR wastes space
CREATE TABLE users (
  id VARCHAR(36) PRIMARY KEY  -- 36+ bytes
);

-- ✅ Fast: BINARY is compact
CREATE TABLE users (
  id BINARY(16) PRIMARY KEY  -- 16 bytes
);

-- Usage
INSERT INTO users VALUES (UUID_TO_BIN('550e8400-e29b-41d4-a716-446655440000'));

SELECT BIN_TO_UUID(id) FROM users;

Performance comparison:

-- VARCHAR(36) storage for 1M users
SELECT
  TABLE_NAME,
  DATA_LENGTH / 1024 / 1024 as 'Data Size MB',
  INDEX_LENGTH / 1024 / 1024 as 'Index Size MB'
FROM information_schema.TABLES
WHERE TABLE_NAME = 'users_varchar';

-- Result:
-- Data: 52 MB
-- Index: 48 MB
-- Total: 100 MB

-- BINARY(16) storage for 1M users
-- Result:
-- Data: 23 MB
-- Index: 22 MB
-- Total: 45 MB

-- 55% space savings!

PostgreSQL native type:

-- ✅ Use native UUID type
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid()
);

-- Native type benefits:
-- - Automatic validation
-- - 16 bytes storage
-- - Optimized comparisons
-- - Built-in functions

Never Use for Security Tokens

❌ Wrong: UUID for password reset:

// INSECURE!
const resetToken = crypto.randomUUID();

await db.passwordResets.insert({
  userId,
  token: resetToken,
  expiresAt: Date.now() + 3600000
});

await sendEmail(user.email, {
  link: `https://example.com/reset/${resetToken}`
});

// Problem: UUIDs have 122 bits of randomness
// Better security tokens have 256+ bits

✅ Right: Use crypto.randomBytes:

const crypto = require('crypto');

// Generate 256 bits of randomness
const resetToken = crypto.randomBytes(32).toString('hex');
// 64 characters: a1b2c3d4e5f6...

await db.passwordResets.insert({
  userId,
  token: crypto.createHash('sha256').update(resetToken).digest('hex'),  // Hash before storing
  expiresAt: Date.now() + 3600000
});

await sendEmail(user.email, {
  link: `https://example.com/reset/${resetToken}`
});

Security comparison:

Token Type Bits Brute Force Time Use For
UUID v4 122 ~2^61 attempts Regular IDs
256-bit token 256 ~2^128 attempts Security tokens
128-bit token 128 ~2^64 attempts Session IDs

What to use UUIDs for:

  • ✅ Database primary keys
  • ✅ API request IDs
  • ✅ Resource identifiers
  • ✅ File names

What NOT to use UUIDs for:

  • ❌ Password reset tokens
  • ❌ Email verification tokens
  • ❌ API keys
  • ❌ Encryption keys
  • ❌ Cryptographic nonces

Always Validate User Input

API endpoint validation:

const { validate: isUUID } = require('uuid');

app.get('/users/:id', (req, res) => {
  // ✅ Validate before using
  if (!isUUID(req.params.id)) {
    return res.status(400).json({
      error: 'Invalid user ID format'
    });
  }

  // Now safe to use
  const user = await db.users.findById(req.params.id);
  res.json(user);
});

Database query safety:

// ❌ Wrong: No validation
async function deleteUser(id) {
  await db.query(`DELETE FROM users WHERE id = '${id}'`);
  // SQL injection risk if id is not validated!
}

// ✅ Right: Validate + parameterized query
async function deleteUser(id) {
  if (!isValidUUID(id)) {
    throw new Error('Invalid UUID');
  }

  await db.query('DELETE FROM users WHERE id = ?', [id]);
}

Form input validation:

const { validate } = require('uuid');

function UserForm() {
  const [error, setError] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    const userId = e.target.userId.value;

    if (!validate(userId)) {
      setError('Invalid user ID format');
      return;
    }

    // Proceed with submission
  };
}

Generate UUIDs Server-Side

❌ Wrong: Trust client-generated IDs:

app.post('/users', async (req, res) => {
  // Client could send:
  // - Duplicate UUIDs
  // - Invalid formats
  // - Malicious data

  await db.users.insert({
    id: req.body.id,  // ❌ Dangerous!
    name: req.body.name
  });
});

✅ Right: Generate server-side:

app.post('/users', async (req, res) => {
  const userId = crypto.randomUUID();  // ✅ Server controls ID

  const user = await db.users.insert({
    id: userId,
    name: req.body.name
  });

  res.json(user);
});

Exception: Client-generated for offline-first apps:

// Mobile app - offline-first
const userId = generateUUID();  // Client generates

// Save locally
await localDB.users.insert({ id: userId, name: 'John' });

// Sync to server later
await fetch('/api/sync', {
  method: 'POST',
  body: JSON.stringify({
    users: [{ id: userId, name: 'John' }]
  })
});

// Server validates and deduplicates
app.post('/api/sync', async (req, res) => {
  for (const user of req.body.users) {
    if (!isValidUUID(user.id)) {
      continue;  // Skip invalid
    }

    // Upsert (insert if not exists)
    await db.users.insertOrUpdate(user);
  }
});

Index Optimization

Create proper indexes:

-- ✅ Primary key index (automatic)
CREATE TABLE users (
  id BINARY(16) PRIMARY KEY
);

-- ✅ Foreign key indexes (manual)
CREATE TABLE orders (
  id BINARY(16) PRIMARY KEY,
  user_id BINARY(16) NOT NULL,
  INDEX idx_user_id (user_id)  -- Essential!
);

-- ✅ Covering indexes for common queries
CREATE INDEX idx_user_email ON users(user_id, email);

-- Query uses index only (no table lookup)
SELECT email FROM users WHERE user_id = UUID_TO_BIN('...');

Avoid redundant indexes:

-- ❌ Redundant
CREATE INDEX idx_user ON orders(user_id);
CREATE INDEX idx_user_status ON orders(user_id, status);
-- Second index makes first one redundant

-- ✅ Just use composite
CREATE INDEX idx_user_status ON orders(user_id, status);
-- Covers both queries:
-- - WHERE user_id = ...
-- - WHERE user_id = ... AND status = ...

Logging and Debugging

Include UUIDs in logs:

const logger = require('pino')();

app.get('/users/:id', async (req, res) => {
  const userId = req.params.id;

  logger.info({ userId }, 'Fetching user');

  try {
    const user = await db.users.findById(userId);
    logger.info({ userId, found: !!user }, 'User fetch complete');
    res.json(user);
  } catch (error) {
    logger.error({ userId, error }, 'Failed to fetch user');
    res.status(500).json({ error: 'Internal error' });
  }
});

// Logs:
// {"userId":"550e8400-e29b-41d4-a716-446655440000","msg":"Fetching user"}
// {"userId":"550e8400-e29b-41d4-a716-446655440000","found":true,"msg":"User fetch complete"}

Distributed tracing:

// Generate trace ID at entry point
app.use((req, res, next) => {
  req.traceId = req.headers['x-trace-id'] || crypto.randomUUID();
  res.setHeader('x-trace-id', req.traceId);
  next();
});

// Pass through all service calls
async function callExternalService(endpoint, data) {
  return fetch(endpoint, {
    headers: {
      'x-trace-id': req.traceId  // Propagate trace ID
    },
    body: JSON.stringify(data)
  });
}

// Grep logs across all services
// grep "550e8400-e29b-41d4-a716-446655440000" service-*.log

Tools: Debug UUID issues with our UUID Generator and analyze logs with our Regex Tester.

Common UUID Mistakes (and How to Fix Them)

Avoid these pitfalls that trip up even experienced developers.

Using v1 UUIDs (Privacy Leak)

The problem:

const { v1 } = require('uuid');

const userId = v1();
// 6ba7b810-9dad-11d1-80b4-00c04fd430c8
//                           ↑
// This reveals your MAC address!

// Attacker can:
// 1. Identify hardware
// 2. Track creation time
// 3. Correlate activity across services

Real privacy breach:

// User creates account
const userId = v1();  // 6ba7b810-9dad-11d1-80b4-00c04fd430c8

// Later posts anonymously on forum
const postId = v1();  // 6ba7b811-9dad-11d1-80b4-00c04fd430c8

// Same MAC address + similar timestamp = same user!
// Your "anonymous" post is now linked to your account

✅ Fix: Use v4 instead:

const userId = crypto.randomUUID();  // v4
// 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d
// ✅ Completely random, no privacy leak

Storing as VARCHAR Instead of BINARY

The performance killer:

-- ❌ Wastes 2.25x space
CREATE TABLE users (
  id VARCHAR(36) PRIMARY KEY
);

-- 1 million rows:
-- IDs: 36 MB
-- Index: 50+ MB
-- Total: 86+ MB

-- Queries slower due to string comparison
SELECT * FROM users WHERE id = '550e8400-e29b-41d4-a716-446655440000';

✅ Fix: Use BINARY(16):

CREATE TABLE users (
  id BINARY(16) PRIMARY KEY
);

-- 1 million rows:
-- IDs: 16 MB
-- Index: 22 MB
-- Total: 38 MB
-- 56% space savings!

-- Faster binary comparison
SELECT * FROM users WHERE id = UUID_TO_BIN('550e8400-e29b-41d4-a716-446655440000');

Helper functions:

-- Create convenience functions
DELIMITER $$

CREATE FUNCTION BIN_UUID_V4() RETURNS BINARY(16)
DETERMINISTIC
BEGIN
  RETURN UUID_TO_BIN(UUID(), 1);
END$$

CREATE FUNCTION UUID_FROM_BIN(b BINARY(16)) RETURNS CHAR(36)
DETERMINISTIC
BEGIN
  RETURN BIN_TO_UUID(b, 1);
END$$

DELIMITER ;

-- Usage
INSERT INTO users VALUES (BIN_UUID_V4(), 'John');
SELECT UUID_FROM_BIN(id), name FROM users;

Not Validating UUID Format

The security risk:

// ❌ No validation - SQL injection risk
app.get('/users/:id', async (req, res) => {
  const query = `SELECT * FROM users WHERE id = '${req.params.id}'`;
  const user = await db.query(query);
  res.json(user);
});

// Attacker sends:
// GET /users/' OR '1'='1' --
// SQL becomes: SELECT * FROM users WHERE id = '' OR '1'='1' --'
// Returns all users!

✅ Fix: Always validate:

const { validate } = require('uuid');

app.get('/users/:id', async (req, res) => {
  if (!validate(req.params.id)) {
    return res.status(400).json({ error: 'Invalid UUID' });
  }

  const user = await db.query(
    'SELECT * FROM users WHERE id = ?',
    [req.params.id]
  );

  res.json(user);
});

Assuming UUIDs Are Sortable

The wrong assumption:

-- ❌ Won't work: UUIDs are not chronologically ordered
SELECT * FROM events
WHERE id > '550e8400-e29b-41d4-a716-446655440000'
ORDER BY id
LIMIT 10;

-- This does NOT give you next 10 events!
-- UUID comparison is lexicographic, not chronological

✅ Fix: Use timestamp column:

CREATE TABLE events (
  id BINARY(16) PRIMARY KEY,
  event_type VARCHAR(50),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

  INDEX idx_created (created_at)
);

-- ✅ Correct: Sort by timestamp
SELECT * FROM events
WHERE created_at > '2025-01-01 00:00:00'
ORDER BY created_at
LIMIT 10;

Or use ULID for sortable IDs:

const { ulid } = require('ulid');

const eventId = ulid();  // Sortable by time
// 01ARZ3NDEKTSV4RRFFQ69G5FAV

// Store events
await db.events.insert({ id: eventId, type: 'click' });

// Query chronologically
SELECT * FROM events
WHERE id > '01ARZ3NDEK'
ORDER BY id;  // ✅ Works!

Generating UUIDs in Loops Without Performance Consideration

The performance problem:

// ❌ Slow: Generating millions of UUIDs
const users = [];
for (let i = 0; i < 1000000; i++) {
  users.push({
    id: crypto.randomUUID(),  // Expensive!
    name: `User ${i}`
  });
}
await db.users.insertMany(users);

✅ Fix: Batch generation or use database defaults:

// Option 1: Let database generate
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name VARCHAR(100)
);

INSERT INTO users (name)
SELECT 'User ' || generate_series(1, 1000000);
-- PostgreSQL generates UUIDs efficiently

// Option 2: Batch inserts
const BATCH_SIZE = 10000;
for (let i = 0; i < 1000000; i += BATCH_SIZE) {
  const batch = [];
  for (let j = 0; j < BATCH_SIZE; j++) {
    batch.push({
      id: crypto.randomUUID(),
      name: `User ${i + j}`
    });
  }
  await db.users.insertMany(batch);
}

Collision Paranoia (Over-Engineering)

The unnecessary check:

// ❌ Unnecessary: Checking for UUID collisions
async function createUser(name) {
  let userId;
  let exists = true;

  // This loop will almost never run more than once!
  while (exists) {
    userId = crypto.randomUUID();
    exists = await db.users.exists({ id: userId });
  }

  await db.users.insert({ id: userId, name });
}

// You're wasting database queries!
// UUID v4 collision probability is ~0% in practice

The math:

Probability of collision with UUID v4:
- After 1 billion UUIDs: ~0.0000000001%
- After 1 trillion UUIDs: ~0.0001%

You'd need to generate 2.71 quintillion UUIDs
to have a 50% chance of collision.

If you generate 1 million UUIDs per second:
- It would take 85,000+ years

✅ Fix: Just handle the error (will never happen):

async function createUser(name) {
  const userId = crypto.randomUUID();

  try {
    await db.users.insert({ id: userId, name });
  } catch (error) {
    if (error.code === 'ER_DUP_ENTRY') {
      // UUID collision (will literally never happen)
      // But handle it anyway for completeness
      logger.alert('UUID collision detected!', { userId });
      return createUser(name);  // Retry
    }
    throw error;
  }
}

Exposing UUIDs in URLs Without Thought

The problem:

❌ Ugly, long URLs:
https://example.com/share/550e8400-e29b-41d4-a716-446655440000

Better alternatives exist for public URLs

✅ Fix: Use shorter IDs for public URLs:

const { nanoid } = require('nanoid');

// Use NanoID for short links
app.post('/share', async (req, res) => {
  const shareId = nanoid(8);  // 8 characters
  const internalId = crypto.randomUUID();  // Internal UUID

  await db.shares.insert({
    id: internalId,  // UUID for DB
    shortCode: shareId,  // NanoID for URL
    content: req.body.content
  });

  res.json({
    url: `https://example.com/share/${shareId}`  // Short!
  });
});

// Result: https://example.com/share/iR5a-VaY

Or use base64url encoding:

function shortenUUID(uuid) {
  // Remove hyphens and convert to buffer
  const hex = uuid.replace(/-/g, '');
  const buffer = Buffer.from(hex, 'hex');

  // Encode as base64url (22 characters)
  return buffer.toString('base64url');
}

function expandUUID(short) {
  const buffer = Buffer.from(short, 'base64url');
  const hex = buffer.toString('hex');

  // Add hyphens back
  return `${hex.slice(0,8)}-${hex.slice(8,12)}-${hex.slice(12,16)}-${hex.slice(16,20)}-${hex.slice(20)}`;
}

// Usage
const uuid = '550e8400-e29b-41d4-a716-446655440000';
const short = shortenUUID(uuid);
// 'VQ6EAOKbQdSnFkRmVUQAAA'

const original = expandUUID(short);
// '550e8400-e29b-41d4-a716-446655440000'

// URL: https://example.com/users/VQ6EAOKbQdSnFkRmVUQAAA

Tools: Fix UUID issues with our UUID Generator and validate formats with our UUID Validator.

Frequently Asked Questions

Are UUIDs Truly Unique?

Short answer: Yes, for all practical purposes.

Long answer: UUID v4 uses 122 bits of randomness. The probability of collision is so low that you can treat them as unique.

Collision probability:

Number of UUIDs needed for 50% collision chance:
2^61 = 2,305,843,009,213,693,952 (2.3 quintillion)

For perspective:
- If every person on Earth (8 billion) generates 1 million UUIDs
- Total: 8 quadrillion UUIDs
- Collision probability: ~0.0014%

Real-world comparison:

  • Winning Powerball lottery: 1 in 292 million
  • UUID collision (after 1 billion): 1 in 10 billion billion
  • You’re 34 million times more likely to win Powerball!

Should you check for collisions?
No. The database will reject duplicates anyway (primary key constraint), and it will never happen.

What’s the Collision Probability?

By number of UUIDs generated:

UUIDs Generated Collision Probability
1 billion (10^9) 0.00000000026 (1 in 3.8 billion)
1 trillion (10^12) 0.00000026 (1 in 3.8 million)
103 trillion 1%
2.71 quintillion 50%

Formula:

P(collision) ≈ n² / (2 × 2^122)

where n = number of UUIDs generated

Practical example:

// Generate 1 billion UUIDs
// Storage: ~36 GB (if VARCHAR)
// Collision probability: 0.00000000026%

// You're more likely to:
// - Be struck by lightning (1 in 15,300)
// - Win Olympic gold medal (1 in 662,000)
// - Find a four-leaf clover (1 in 10,000)

Can I Use UUIDs as Database Primary Keys?

Yes! But optimize storage:

✅ Do:

-- PostgreSQL
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid()
);

-- MySQL
CREATE TABLE users (
  id BINARY(16) PRIMARY KEY
);

❌ Don’t:

-- Wastes space and slows queries
CREATE TABLE users (
  id VARCHAR(36) PRIMARY KEY
);

Considerations:

Pros:

  • ✅ No coordination needed across systems
  • ✅ Offline generation
  • ✅ Merge-friendly
  • ✅ Privacy (can’t enumerate)

Cons:

  • ❌ Larger than integers (16 bytes vs 8 bytes)
  • ❌ Not sortable (use ULID if needed)
  • ❌ Random index insertion (vs sequential integers)

Performance tip:

-- Use covering indexes
CREATE INDEX idx_user_email ON users(id, email, name);

-- Query uses index only (no table lookup)
SELECT email, name FROM users WHERE id = '...';

How Do I Shorten a UUID?

Option 1: Base64URL encoding (36 → 22 characters):

function shortenUUID(uuid) {
  const hex = uuid.replace(/-/g, '');
  return Buffer.from(hex, 'hex').toString('base64url');
}

function expandUUID(short) {
  const hex = Buffer.from(short, 'base64url').toString('hex');
  return `${hex.slice(0,8)}-${hex.slice(8,12)}-${hex.slice(12,16)}-${hex.slice(16,20)}-${hex.slice(20)}`;
}

// Usage
const uuid = '550e8400-e29b-41d4-a716-446655440000';
const short = shortenUUID(uuid);
// 'VQ6EAOKbQdSnFkRmVUQAAA' (22 chars)

// URL: https://example.com/users/VQ6EAOKbQdSnFkRmVUQAAA

Option 2: Use NanoID instead:

const { nanoid } = require('nanoid');

const id = nanoid();  // 21 characters
// 'V1StGXR8_Z5jdHi6B-myT'

// 42% shorter than UUID
// Still collision-resistant

Option 3: Base62 encoding:

const base62 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';

function uuidToBase62(uuid) {
  let num = BigInt('0x' + uuid.replace(/-/g, ''));
  let encoded = '';

  while (num > 0) {
    encoded = base62[num % 62n] + encoded;
    num = num / 62n;
  }

  return encoded;
}

const uuid = '550e8400-e29b-41d4-a716-446655440000';
const short = uuidToBase62(uuid);
// '2qLsgAHJznwKgV1VfC' (18-22 chars)

UUID vs GUID: What’s the Difference?

They’re the same thing.

Aspect UUID GUID
Standard RFC 4122 Microsoft term
Format 8-4-4-4-12 hex 8-4-4-4-12 hex
Size 128 bits 128 bits
Usage Universal .NET, Windows, SQL Server

Example:

// JavaScript: UUID
const uuid = crypto.randomUUID();

// C#: GUID
Guid guid = Guid.NewGuid();

// Same format!
// 550e8400-e29b-41d4-a716-446655440000

Only difference: Historical byte ordering in some Microsoft implementations (modern versions fixed this).

Use “UUID” in new code - it’s the universal standard.

Can UUIDs Be Sequential/Sortable?

UUID v4: No - They’re random.

Alternatives for sortable IDs:

1. ULID (best choice):

const { ulid } = require('ulid');

const id1 = ulid();  // 01ARZ3NDEKTSV4RRFFQ69G5FAV
const id2 = ulid();  // 01ARZ3NDEL000000000000000

// id1 < id2 ✅ (sortable)

2. UUID v1 (not recommended):

// Contains timestamp but leaks MAC address
const { v1 } = require('uuid');
const id = v1();  // Sortable but privacy issue

3. Time-based UUID (custom):

function timeUUID() {
  const timestamp = Date.now().toString(16).padStart(12, '0');
  const random = crypto.randomBytes(10).toString('hex');

  return `${timestamp.slice(0,8)}-${timestamp.slice(8,12)}-4${random.slice(0,3)}-${random.slice(3,7)}-${random.slice(7)}`;
}

const id1 = timeUUID();
const id2 = timeUUID();
// Sortable by creation time

4. Snowflake ID:

const Snowflake = require('snowflake-id');
const snowflake = new Snowflake({ worker_id: 1 });

const id1 = snowflake.generate();  // 175928847299117063
const id2 = snowflake.generate();  // 175928847299117064
// Sequential and sortable

Recommendation: Use ULID for sortable unique IDs.

Tools: Generate and compare UUIDs with our UUID Generator and explore alternatives with our Random String Generator.

Conclusion: Master UUIDs for Production Systems

You now have everything you need to use UUIDs correctly in production applications.

Core principles:

  1. Use UUID v4 by default - Random, secure, no privacy issues
  2. Store as BINARY(16) in databases - 56% space savings vs VARCHAR
  3. Always validate input - Never trust client-generated IDs
  4. Never use v1 in production - Leaks MAC address
  5. Consider alternatives - ULID for sortable, NanoID for short URLs
  6. Understand collision probability - Effectively zero, don’t worry about it
  7. Optimize indexes - Use covering indexes, foreign key indexes

Your UUID checklist:

Before implementation:

  • ✅ Choose right version (v4 for general use, v5 for deterministic)
  • ✅ Plan database storage (BINARY(16) or UUID type)
  • ✅ Consider sortability requirements (ULID if needed)
  • ✅ Define validation strategy

During development:

  • ✅ Validate all UUID inputs
  • ✅ Generate server-side (don’t trust client)
  • ✅ Use parameterized queries
  • ✅ Add proper indexes
  • ✅ Include in logs for tracing

In production:

  • ✅ Monitor query performance
  • ✅ Track index usage
  • ✅ Log with request IDs
  • ✅ Handle edge cases (invalid format)

Your UUID Toolkit

Essential tools:

Generation & Validation:

Development:

Database:

All tools: Developer Tools

Quick Reference

Generate UUID:

// Modern JavaScript
const id = crypto.randomUUID();

// Node.js with uuid library
const { v4 } = require('uuid');
const id = v4();

// Python
import uuid
id = uuid.uuid4()

// Java
UUID id = UUID.randomUUID();

// C#
Guid id = Guid.NewGuid();

// Go
id := uuid.New()

// PHP
$id = Uuid::uuid4();

// Ruby
id = SecureRandom.uuid

Validate UUID:

const regex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

function isValidUUID(uuid) {
  return regex.test(uuid);
}

Store in MySQL:

CREATE TABLE users (
  id BINARY(16) PRIMARY KEY
);

INSERT INTO users VALUES (UUID_TO_BIN(UUID()));
SELECT BIN_TO_UUID(id) FROM users;

Store in PostgreSQL:

CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid()
);

Next Steps

  1. Start using UUIDs in your next project
  2. Optimize existing databases - Convert VARCHAR to BINARY
  3. Add validation to all UUID inputs
  4. Implement request tracing with UUID trace IDs
  5. Consider ULID for time-series data
  6. Share this guide with your team

Additional Resources

Official Documentation:

Libraries:

Related Articles:

Database Documentation:


Found this helpful? Bookmark our developer tools and share this guide with your team.

Have questions about UUIDs? Contact us or check our FAQ.


Last Updated: November 9, 2025
Reading Time: 18 minutes
Author: Orbit2x Team

Share This Guide

Related Articles

Continue learning with these related posts

Found This Guide Helpful?

Try our free developer tools that power your workflow. No signup required, instant results.

Share This Article

Help others discover this guide

Share:

Stay Updated

Get notified about new guides and tools