Developer Guide

UUID Guide: Understanding
Versions, Formats & Best Practices

Everything you need to know about UUIDs. Learn the differences between versions, when to use each type, and how to implement them correctly.

14 min read Updated Jan 2026

1. What is a UUID?

UUID (Universally Unique Identifier) is a 128-bit identifier designed to be unique across all computers, all time, without requiring a central authority. Also known as GUID (Globally Unique Identifier) in Microsoft ecosystems.

Example UUIDs:

550e8400-e29b-41d4-a716-446655440000 (v1) f47ac10b-58cc-4372-a567-0e02b2c3d479 (v4) 01932c08-e844-7d3f-8d5e-9c1b5d62a3e8 (v7)

Key Characteristics:

  • 128 bits: Represented as 32 hexadecimal characters
  • Globally unique: No central coordination needed
  • Standardized: Defined in RFC 4122 (and RFC 9562 for v7)
  • Platform-independent: Works across all systems

Common Use Cases:

  • Database primary keys
  • Distributed system identifiers
  • Session tokens and correlation IDs
  • File and resource naming
  • API request tracking

2. UUID Format and Structure

UUIDs follow a standard format: 32 hexadecimal digits displayed as 8-4-4-4-12 groups separated by hyphens.

UUID Structure

xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
time_low (32 bits)
time_mid (16 bits)
time_hi_and_version (16 bits) - M = version
clock_seq_hi_and_res (16 bits) - N = variant
node (48 bits)

Version and Variant Indicators:

Look at the 13th character (M) to identify the version:

  • xxxxxxxx-xxxx-1xxx-xxxx-xxxxxxxxxxxx → Version 1
  • xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx → Version 4
  • xxxxxxxx-xxxx-7xxx-xxxx-xxxxxxxxxxxx → Version 7

The variant (N) indicates the UUID variant. For standard UUIDs, it starts with 8, 9, a, or b.

3. UUID Versions Explained

Version Based On Sortable Recommended
v1 Timestamp + MAC address Partially ⚠️ Privacy concern
v2 DCE Security No ❌ Rarely used
v3 MD5 hash of namespace + name No ⚠️ Use v5 instead
v4 Random No ✅ Good default
v5 SHA-1 hash of namespace + name No ✅ Deterministic
v6 Reordered v1 (time-sortable) Yes ✅ When v1 sorting needed
v7 Unix timestamp + random Yes ✅ Modern choice

Version 1: Timestamp + MAC Address

Combines 60-bit timestamp (100-nanosecond intervals since Oct 15, 1582) with MAC address. Privacy concern: Exposes when and where the UUID was created.

// v1 structure reveals information
f81d4fae-7dec-11d0-a765-00a0c91e6bf6
           ↑          ↑
     timestamp    MAC address

Version 4: Random

122 bits of randomness (6 bits reserved for version/variant). Most commonly used. Simple, no information leakage, but not sortable.

// v4 - pure randomness
f47ac10b-58cc-4372-a567-0e02b2c3d479
              ↑    ↑
           version variant (4 and 8/9/a/b)

Version 5: Name-Based (SHA-1)

Deterministic: same namespace + name always produces the same UUID. Useful for generating consistent IDs from known data.

// Same input = same UUID
uuid_v5(DNS_NAMESPACE, "example.com") → always same result
uuid_v5(URL_NAMESPACE, "https://example.com/page") → always same result

Version 7: Time-Ordered Random (Recommended)

48-bit Unix timestamp (milliseconds) + 74 bits of randomness. Best of both worlds: sortable by creation time while being mostly random. Ideal for database primary keys.

// v7 - sortable by time, generated at the same millisecond
01932c08-e844-7d3f-8d5e-9c1b5d62a3e8 (earlier)
01932c08-e845-7a2b-9f3c-8d4e7a6b5c9d (later within same ms)
         ↑
    timestamp (ms since Unix epoch)

4. Which UUID Version Should I Use?

🏆 UUID v7 - Modern Applications

Best choice for new projects. Time-sortable for database efficiency, random enough for security, and chronologically meaningful. Use this if your language/library supports it.

✅ UUID v4 - Simple & Secure

Great default choice. Maximum randomness, no information leakage, widely supported. Choose v4 if you don't need sorting or if v7 isn't available.

🔗 UUID v5 - Deterministic IDs

When you need the same ID for the same input (idempotency). Great for creating IDs from URLs, emails, or other unique strings.

⚠️ UUID v1 - Legacy Only

Avoid for new projects. Exposes MAC address (privacy) and creation time. Only use for compatibility with existing v1 systems.

Decision Flowchart:

Need deterministic IDs? → UUID v5
Need time-sortable IDs? → UUID v7
Need maximum randomness? → UUID v4
Legacy system compatibility? → Match existing version
Default for new projects → UUID v7 (or v4 if unsupported)

5. Collision Probability

The question everyone asks: Can two UUIDs ever be the same?

TL;DR: Practically Impossible

UUID v4 has 122 random bits = 5.3 × 1036 possible values. You'd need to generate 2.71 quintillion (2.71 × 1018) UUIDs for a 50% collision chance.

The Math:

Possible UUIDs = 2122 ≈ 5,316,911,983,139,663,491,615,228,241,121,378,304

That's over 5 undecillion unique values

Perspective:

  • Generating 1 billion UUIDs per second for 86 years gives ~50% collision chance
  • Generating 1 trillion UUIDs = 0.00000000006% collision chance
  • You're more likely to be struck by lightning while winning the lottery

⚠️ The Real Risk

Collisions almost always come from bad random number generators, not mathematical probability. Always use cryptographically secure randomness (CSPRNG) for UUID generation.

6. UUIDs in Databases

Pros of UUID Primary Keys:

  • ✅ Generate IDs client-side (no round-trip to database)
  • ✅ Easy database merging and sharding
  • ✅ No enumeration attacks (can't guess next ID)
  • ✅ Hide row count from API consumers

Cons of UUID Primary Keys:

  • ❌ Larger than integers (16 bytes vs 4-8 bytes)
  • ❌ Random UUIDs cause index fragmentation (B-tree)
  • ❌ Less human-readable for debugging
  • ❌ Slightly slower joins and lookups

Database-Specific Storage:

Database Native Type Storage
PostgreSQL UUID 16 bytes (native)
MySQL 8.0+ BINARY(16) 16 bytes
SQL Server UNIQUEIDENTIFIER 16 bytes (native)
SQLite TEXT or BLOB 36 or 16 bytes
MongoDB BinData subtype 4 16 bytes

PostgreSQL Example:

-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- Create table with UUID primary key
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    -- or for v7: gen_random_uuid() in PG 13+
    email VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);

-- Insert with auto-generated UUID
INSERT INTO users (email) VALUES ('user@example.com');

-- Or specify UUID
INSERT INTO users (id, email) 
VALUES ('550e8400-e29b-41d4-a716-446655440000', 'user2@example.com');

💡 Pro Tip: Use UUID v7 for Database Keys

UUID v7 is time-ordered, which means new records are always inserted at the end of the B-tree index. This eliminates the fragmentation problem of random UUIDs while keeping all the benefits.

7. Implementation Examples

JavaScript / Node.js

// Node.js (built-in since v14.17.0)
import { randomUUID } from 'crypto';
const uuid = randomUUID(); // v4

// Browser (modern browsers)
const uuid = crypto.randomUUID(); // v4

// Using uuid library (supports all versions)
import { v4 as uuidv4, v5 as uuidv5, v7 as uuidv7 } from 'uuid';

const id = uuidv4();  // Random
const id7 = uuidv7(); // Time-ordered (v7)

// v5 - deterministic from namespace + name
const DNS_NAMESPACE = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
const id5 = uuidv5('example.com', DNS_NAMESPACE);

// Validate UUID
function isValidUUID(str) {
    const regex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-7][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
    return regex.test(str);
}

Python

import uuid

# UUID v4 - Random
id_v4 = uuid.uuid4()
print(id_v4)  # e.g., f47ac10b-58cc-4372-a567-0e02b2c3d479

# UUID v1 - Timestamp + MAC (avoid for privacy)
id_v1 = uuid.uuid1()

# UUID v5 - Name-based (SHA-1)
id_v5 = uuid.uuid5(uuid.NAMESPACE_DNS, 'example.com')
# Always produces: cfbff0d1-9375-5685-968c-48ce8b15ae17

# UUID v7 (Python 3.12+)
# id_v7 = uuid.uuid7()

# Convert to/from string
str_uuid = str(id_v4)
parsed = uuid.UUID(str_uuid)

# Get bytes (for database storage)
uuid_bytes = id_v4.bytes  # 16 bytes

Java

import java.util.UUID;

public class UuidExample {
    public static void main(String[] args) {
        // UUID v4 - Random
        UUID uuid = UUID.randomUUID();
        System.out.println(uuid);
        
        // UUID v3/v5 - Name-based
        UUID namespace = UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
        UUID nameUuid = UUID.nameUUIDFromBytes("example.com".getBytes());
        
        // Parse from string
        UUID parsed = UUID.fromString("550e8400-e29b-41d4-a716-446655440000");
        
        // Get version
        int version = uuid.version(); // 4
        
        // Convert to bytes (for database)
        long msb = uuid.getMostSignificantBits();
        long lsb = uuid.getLeastSignificantBits();
    }
}

Go

package main

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

func main() {
    // UUID v4
    id := uuid.New()
    fmt.Println(id) // e.g., 6ba7b810-9dad-11d1-80b4-00c04fd430c8
    
    // UUID v7 (time-ordered)
    id7, _ := uuid.NewV7()
    fmt.Println(id7)
    
    // Parse from string
    parsed, _ := uuid.Parse("550e8400-e29b-41d4-a716-446655440000")
    
    // Check version
    fmt.Println(parsed.Version()) // 1, 4, 7, etc.
}

PHP

<?php
// Using ramsey/uuid library
use Ramsey\Uuid\Uuid;

// UUID v4 - Random
$uuid = Uuid::uuid4();
echo $uuid->toString(); // e.g., f47ac10b-58cc-4372-a567-0e02b2c3d479

// UUID v7 - Time-ordered
$uuid7 = Uuid::uuid7();

// UUID v5 - Name-based
$uuid5 = Uuid::uuid5(Uuid::NAMESPACE_DNS, 'example.com');

// Parse and validate
$parsed = Uuid::fromString('550e8400-e29b-41d4-a716-446655440000');
$isValid = Uuid::isValid($someString);

// Get bytes for database
$bytes = $uuid->getBytes(); // 16 bytes
?>

8. UUID Alternatives

Format Length Sortable Notes
UUID v4 36 chars No Standard, widely supported
UUID v7 36 chars Yes Time-sorted, new standard
ULID 26 chars Yes Crockford Base32, URL-safe
nanoid 21 chars No Compact, URL-safe
CUID2 24 chars Partially Secure, collision-resistant
Snowflake 64-bit int Yes Twitter's format, needs coordination

ULID Example:

// ULID format: 01ARZ3NDEKTSV4RRFFQ69G5FAV
// 48-bit timestamp + 80-bit random
// Lexicographically sortable, URL-safe

import { ulid } from 'ulid';
const id = ulid(); // 01ARZ3NDEKTSV4RRFFQ69G5FAV

nanoid Example:

// nanoid: V1StGXR8_Z5jdHi6B-myT
// Smaller, faster, URL-safe

import { nanoid } from 'nanoid';
const id = nanoid(); // V1StGXR8_Z5jdHi6B-myT

9. Best Practices

✅ Do: Use UUID v7 for new database primary keys

Time-ordered UUIDs prevent index fragmentation while maintaining all benefits of UUIDs.

✅ Do: Store as binary when possible

16 bytes binary vs 36 bytes string saves storage and improves index performance.

✅ Do: Use v5 for deterministic IDs

When you need idempotent ID generation from known data (URLs, emails, etc.).

❌ Don't: Use UUID v1 in new applications

Exposes MAC address and exact creation timestamp. Privacy and security concern.

❌ Don't: Rely on UUID ordering with v4

Random UUIDs have no guaranteed order. Use v7 if you need time-based sorting.

❌ Don't: Create your own UUID generation

Use well-tested libraries. DIY implementations often have weak randomness.

10. Frequently Asked Questions

What is the difference between UUID and GUID?
They're the same thing with different names. UUID (Universally Unique Identifier) is the standard term from RFC 4122. GUID (Globally Unique Identifier) is Microsoft's terminology. Both use the same 128-bit format and are interchangeable.
Are UUIDs truly unique?
Practically yes. While collisions are theoretically possible, the probability is so low (1 in 5.3×1036 for v4) that you're more likely to experience multiple unrelated impossible events simultaneously. Use proper libraries with cryptographic randomness, and collisions won't be a concern.
Should I use UUIDs or auto-increment integers for primary keys?
UUIDs: Better for distributed systems, no database round-trip for ID generation, harder to guess/enumerate.
Integers: Smaller storage, faster indexes, human-readable for debugging.
Recommendation: Use UUID v7 for most applications. Use integers only if storage/performance is critical and you don't need distributed ID generation.
Can I extract timestamp from a UUID?
Depends on the version. v1, v6, v7: Yes, timestamp is embedded.
v4: No, it's purely random.
For v7, extract the first 48 bits and interpret as Unix milliseconds. Many libraries provide helper functions for this.
Why does UUID v1 expose MAC address?
UUID v1 was designed in 1997 when privacy concerns were different. It uses MAC address to guarantee uniqueness across machines without coordination. Today, this is a privacy leak - attackers can identify which machine generated a UUID. Use v4 or v7 instead.
How do I validate a UUID string?
Use this regex for standard UUIDs:
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-7][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
This validates the format, version (1-7), and variant (8/9/a/b). Most UUID libraries also provide validation functions.
What is a nil UUID?
The nil UUID is all zeros: 00000000-0000-0000-0000-000000000000. It's used to represent the absence of a UUID or as a placeholder. Think of it like null for UUIDs. There's also a max UUID (all f's) sometimes used for upper bounds.
Can I use UUIDs in URLs?
Yes, UUIDs are URL-safe. They only contain hexadecimal characters and hyphens. Example: /users/550e8400-e29b-41d4-a716-446655440000. For shorter URLs, consider ULID or nanoid, or encode UUIDs in Base64 (but you lose readability).
How much storage does a UUID need?
Binary: 16 bytes (128 bits) - most efficient
String with hyphens: 36 bytes (32 hex + 4 hyphens)
String without hyphens: 32 bytes
For databases, always use binary storage when possible. A BIGINT is only 8 bytes by comparison.
Is UUID v7 ready for production use?
Yes! UUID v7 was standardized in RFC 9562 (2024). Major libraries now support it: uuid (JS), Go's standard library, and many others. It combines the benefits of time-sorting with random uniqueness. It's the recommended choice for new applications.

Need to generate UUIDs?

Try our free UUID generator. Create v1, v4, v5, and v7 UUIDs instantly - all client-side.

Open UUID Generator

Related Guides