← Back to Blog

What is UUID and How to Generate One (With Use Cases)

Every distributed system eventually faces the same problem: how do you create a unique identifier for a record without coordinating with a central server? Whether you are assigning IDs to database rows, tracking events across microservices, or generating idempotency keys for API calls, UUID is the most widely adopted answer. This guide covers what UUIDs are, how each version works, how to generate them in every major language, and when they are the right tool versus when you should reach for something else.

What Is a UUID?

UUID stands for Universally Unique Identifier. It is a 128 bit label standardized in RFC 4122 (now superseded by RFC 9562) designed to be unique across space and time without requiring a central authority to coordinate assignment. The format is a sequence of 32 hexadecimal digits displayed in five groups separated by hyphens:

xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx

A concrete example:

550e8400-e29b-41d4-a716-446655440000

The letter M in the format denotes the version (1 through 8), and N encodes the variant field. The total 128 bits give a theoretical address space of 2128 - approximately 340 undecillion (3.4 × 1038) possible values.

UUID Format Breakdown

Taking the UUID 550e8400-e29b-41d4-a716-446655440000:

  • 550e8400 - 8 hex digits (32 bits): time_low (in v1) or random (in v4)
  • e29b - 4 hex digits (16 bits): time_mid
  • 41d4 - 4 hex digits (16 bits): the first digit 4 is the version number
  • a716 - 4 hex digits (16 bits): the first digit a (binary 1010) encodes the variant (RFC 4122 = 10xx)
  • 446655440000 - 12 hex digits (48 bits): node (MAC address in v1, random in v4)

The version and variant bits are the only structured fields. The rest of the bits carry version-specific data - timestamps, random values, or hashes depending on the version.

UUID Versions Explained

UUID v1 - Time-Based

Version 1 UUIDs are generated from the current timestamp (nanoseconds since October 15, 1582 - the Gregorian calendar reform date) combined with a 48-bit node value, typically the MAC address of the generating machine.

// Example v1 UUID
6ba7b810-9dad-11d1-80b4-00c04fd430c8
         ^^^^
         version = 1

Properties: Monotonically increasing within a single machine (because time moves forward). Can be sorted chronologically. However, the MAC address embedded in the node field is a privacy concern - it exposes the hardware identity of the generating server.

Use when: You need sortable UUIDs and are not concerned about exposing the MAC address (e.g., internal distributed systems with no privacy requirements).

UUID v4 - Random

Version 4 is the most widely used UUID type. It consists of 122 bits of cryptographically secure random data (the remaining 6 bits are fixed for the version and variant fields).

// Example v4 UUID
f47ac10b-58cc-4372-a567-0e02b2c3d479
              ^^^^
              version = 4

Properties: No structure, completely random. Cannot be sorted by generation time. Requires a CSPRNG (cryptographically secure pseudo-random number generator) - using Math.random() instead would produce guessable, non-unique results.

Use when: You need globally unique IDs without any structural requirements. The default choice for most web applications, APIs, and databases.

UUID v5 - Name-Based (SHA-1)

Version 5 generates a deterministic UUID by hashing a namespace UUID and a name string using SHA-1. Given the same namespace and name, the output is always the same UUID.

// Namespace: DNS (6ba7b810-9dad-11d1-80b4-00c04fd430c8)
// Name: "www.example.com"
// Output (always the same):
2ed6657d-e927-568b-95e3-af9c65244f65

Properties: Deterministic and reproducible. Useful for content addressing, deduplication, and generating stable IDs from known inputs. Version 3 is identical but uses MD5 instead of SHA-1 - v5 should be preferred.

Use when: You need to derive a consistent UUID from a string input, such as mapping a URL to a stable record ID or deduplicating events by content.

UUID v7 - Time-Ordered Random (New Standard)

Version 7, standardized in RFC 9562 (2024), combines a Unix millisecond timestamp prefix with random bits. The timestamp occupies the most significant 48 bits, making v7 UUIDs naturally sortable by creation time while retaining randomness in the lower bits.

// Example v7 UUID (generated at 2026-03-26T12:00:00.000Z)
018e8e0d-2b8e-7a1f-b432-9c3f7d2e1a05
         ^^^^
         version = 7
// First 12 hex chars encode Unix ms timestamp

Properties: Lexicographically sortable by generation time. No MAC address exposure. Random suffix prevents guessability. Excellent B-tree index locality in databases because sequential inserts go to the same leaf node.

Use when: You want the global uniqueness of v4 with the database-friendly sortability of sequential integers. The recommended choice for new database primary keys in 2026.

How to Generate UUIDs

JavaScript (Browser & Node.js 19+)

The Web Crypto API provides crypto.randomUUID() natively since Chrome 92, Firefox 95, and Node.js 19. It generates a version 4 UUID using the OS CSPRNG:

// Browser and Node.js 19+
const id = crypto.randomUUID();
console.log(id);
// "f47ac10b-58cc-4372-a567-0e02b2c3d479"

// Generate multiple
const ids = Array.from({ length: 5 }, () => crypto.randomUUID());
console.log(ids);
// ["3d6f9a...", "7c2b1e...", "9a4c8f...", "1b5d3a...", "6e8f2c..."]

// Use as a React key or object ID
const record = {
  id: crypto.randomUUID(),
  name: 'Alice',
  createdAt: new Date().toISOString()
};
console.log(record.id); // "a1b2c3d4-e5f6-4789-abcd-ef0123456789"

JavaScript: Custom v4 Implementation (No Dependencies)

If you need to support environments without crypto.randomUUID() (Node.js < 19, older browsers), this implementation uses crypto.getRandomValues() which is available much more broadly:

function uuidv4() {
  // Fill a 16-byte array with cryptographically random values
  const bytes = crypto.getRandomValues(new Uint8Array(16));

  // Set version bits (4) in byte 6: 0100xxxx
  bytes[6] = (bytes[6] & 0x0f) | 0x40;

  // Set variant bits (RFC 4122) in byte 8: 10xxxxxx
  bytes[8] = (bytes[8] & 0x3f) | 0x80;

  // Convert to hex string with hyphens
  const hex = [...bytes].map(b => b.toString(16).padStart(2, '0')).join('');
  return [
    hex.slice(0, 8),
    hex.slice(8, 12),
    hex.slice(12, 16),
    hex.slice(16, 20),
    hex.slice(20, 32)
  ].join('-');
}

console.log(uuidv4()); // "f47ac10b-58cc-4372-a567-0e02b2c3d479"

JavaScript: Using the uuid npm Package

The uuid package supports all UUID versions and is the standard choice when you need v1, v3, v5, or v7:

npm install uuid
import { v4 as uuidv4, v7 as uuidv7, v5 as uuidv5 } from 'uuid';

// v4 random
console.log(uuidv4()); // "f47ac10b-58cc-4372-a567-0e02b2c3d479"

// v7 time-ordered (sortable)
console.log(uuidv7()); // "018e8e0d-2b8e-7000-8000-000000000000"

// v5 deterministic from namespace + name
const DNS_NAMESPACE = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
console.log(uuidv5('www.example.com', DNS_NAMESPACE));
// "2ed6657d-e927-568b-95e3-af9c65244f65" (always the same)

Python

Python's standard library includes a uuid module that covers all common versions:

import uuid

# v4 random
v4 = uuid.uuid4()
print(v4)            # UUID('f47ac10b-58cc-4372-a567-0e02b2c3d479')
print(str(v4))       # "f47ac10b-58cc-4372-a567-0e02b2c3d479"

# v1 time based (includes MAC address)
v1 = uuid.uuid1()
print(v1)            # UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8')

# v5 deterministic
v5 = uuid.uuid5(uuid.NAMESPACE_DNS, 'www.example.com')
print(v5)            # UUID('2ed6657d-e927-568b-95e3-af9c65244f65')

# v7 requires Python 3.12+
v7 = uuid.uuid7()    # UUID('018e8e0d-2b8e-7a1f-b432-9c3f7d2e1a05')

# Access the integer value (128 bit)
print(v4.int)        # 325614984032954027695254...

# Check UUID version
print(v4.version)    # 4

Go

The github.com/google/uuid package is the standard UUID library for Go:

go get github.com/google/uuid
package main

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

func main() {
    // v4 random
    id := uuid.New()
    fmt.Println(id.String())
    // "f47ac10b-58cc-4372-a567-0e02b2c3d479"

    // v5 deterministic
    v5 := uuid.NewSHA1(uuid.NameSpaceDNS, []byte("www.example.com"))
    fmt.Println(v5.String())
    // "2ed6657d-e927-568b-95e3-af9c65244f65"

    // Parse a UUID string
    parsed, err := uuid.Parse("550e8400-e29b-41d4-a716-446655440000")
    if err != nil {
        panic(err)
    }
    fmt.Println(parsed.Version()) // 4
}

Command Line

# Linux (util-linux package)
uuidgen
# f47ac10b-58cc-4372-a567-0e02b2c3d479

# Generate multiple
for i in $(seq 1 5); do uuidgen; done

# macOS (built-in)
uuidgen
# F47AC10B-58CC-4372-A567-0E02B2C3D479  (uppercase by default)
uuidgen | tr '[:upper:]' '[:lower:]'    # lowercase

# Python one-liner (cross-platform)
python3 -c "import uuid; print(uuid.uuid4())"

# Node.js one-liner (Node 19+)
node -e "console.log(crypto.randomUUID())"

Generate UUIDs Instantly

Generate single or bulk UUIDs (v4 and v7) in your browser - no dependencies, no data sent to any server.

Open UUID Generator

Collision Probability: How Unique Is "Universally Unique"?

UUID v4 uses 122 random bits (the other 6 bits are fixed for version and variant). The total number of possible v4 UUIDs is 2122 ≈ 5.3 × 1036.

The birthday problem formula tells us the probability of at least one collision after generating n UUIDs is approximately:

P(collision) ≈ 1 - e^(-n² / (2 * 2^122))

To have a 50% probability of a collision, you would need to generate:
n ≈ 2.71 × 10^18 UUIDs

At a rate of 1 billion UUIDs per second, that would take:
2.71 × 10^18 / 10^9 = 2.71 × 10^9 seconds ≈ 85 years

Practically speaking, UUID v4 collisions are impossible at any scale a real application operates at. The risk of a collision is smaller than the risk of a cosmic ray flipping a bit in your server's RAM.

The only way to get a UUID collision is to use a broken random number generator. Always use crypto.randomUUID() or crypto.getRandomValues() in JavaScript, never Math.random().

UUIDs in Databases

Storing UUIDs: String vs. Binary

UUIDs can be stored as strings (CHAR(36)) or as binary (BINARY(16)). Binary storage is more efficient:

  • CHAR(36): 36 bytes per UUID. Human-readable, easy to debug. Slower index operations because string comparison is character-by-character.
  • BINARY(16): 16 bytes per UUID - 55% smaller. Faster comparisons because they operate on fixed-length byte arrays. Requires converting to/from hex string at the application layer.

Most modern databases have a native UUID type. PostgreSQL's UUID type stores 16 bytes internally while presenting the standard string format to queries. MySQL has UUID() but no dedicated UUID column type - use BINARY(16) with application-level conversion for best performance.

Index Performance: v4 vs. v7

This is the critical practical difference between UUID versions for database use. B-tree indexes (used by PostgreSQL, MySQL, SQLite) work best when new rows are inserted at the end of the index - this is how auto-increment integers behave.

With UUID v4, each new UUID is random, so insertions scatter across the entire index. This causes frequent page splits and random disk I/O, which degrades write performance at high insert volumes. A table with 100 million v4 UUID primary keys can be 3-5x slower to insert into compared to a sequential integer.

With UUID v7, the first 48 bits are a millisecond timestamp, so UUIDs generated in sequence are numerically close to each other. This means new rows cluster at the end of the B-tree index, exactly like auto-increment - no page splits, predictable I/O.

-- PostgreSQL: create a table with UUID v7 primary key
-- (using the pg_uuidv7 extension or application-generated v7)
CREATE TABLE orders (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- v4 (built-in)
    -- or with pg_uuidv7 extension:
    -- id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    amount NUMERIC(10,2) NOT NULL
);

-- MySQL: store UUID as BINARY(16) with conversion functions
CREATE TABLE orders (
    id BINARY(16) PRIMARY KEY DEFAULT (UUID_TO_BIN(UUID(), 1)),
    -- The '1' argument reorders bytes for better index locality
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    amount DECIMAL(10,2) NOT NULL
);

UUID vs. Auto-Increment: When to Use Each

Auto-increment integers (SERIAL in PostgreSQL, AUTO_INCREMENT in MySQL) are still the right choice in many situations. Here is a direct comparison:

  • Auto-increment: Compact (4-8 bytes), perfect index locality, human-readable, but requires a central counter (a database sequence). Two separate databases cannot independently generate non-colliding IDs.
  • UUID v4: 16 bytes (36 as string), globally unique without coordination, safe to generate on any client or server, but random and not sortable by time.
  • UUID v7: 16 bytes, globally unique, time-sortable, database-friendly. The best of both worlds for most new applications.

Choose auto-increment when: single database, no distributed ID generation, storage efficiency is critical, and the ID will never be exposed in URLs (sequential integers are enumerable and expose your data volume).

Choose UUID (v4 or v7) when: distributed systems, IDs generated on clients, merging data from multiple sources, the ID appears in URLs or API responses (non-sequential IDs prevent enumeration attacks), or you use microservices that cannot coordinate on a single sequence.

ULID: An Alternative Worth Knowing

ULID (Universally Unique Lexicographically Sortable Identifier) is an alternative to UUID that is gaining adoption. A ULID looks like:

01ARZ3NDEKTSV4RRFFQ69G5FAV

It is 26 characters (compared to UUID's 36), uses Crockford Base32 encoding (URL safe, case-insensitive), and has the same structure as UUID v7: a 48-bit millisecond timestamp prefix followed by 80 random bits. ULIDs are lexicographically sortable, monotonically increasing within the same millisecond, and more compact than UUID strings. The main drawback is that ULID is not an IETF standard and lacks native database support in most systems.

For new projects in 2026, UUID v7 is the standardized equivalent of ULID and is the recommended choice.

Frequently Asked Questions

What is the difference between UUID v4 and UUID v7?

Both use random bits for uniqueness, but UUID v7 prefixes the random bits with a 48-bit Unix millisecond timestamp. This makes v7 UUIDs sortable by generation time and much more efficient as database primary keys because B-tree indexes can cluster sequential inserts. UUID v4 is purely random with no time component. For new database schemas, v7 is generally the better choice. For non-database use cases (session tokens, idempotency keys, filenames), v4 and v7 are equivalent.

Can I use UUID as a database primary key?

Yes, and it is a common pattern. The main consideration is index performance. UUID v4 causes B-tree index fragmentation at high insert volumes because each new UUID lands at a random position in the index. UUID v7 avoids this because its timestamp prefix ensures sequential ordering. PostgreSQL's native UUID type stores 16 bytes. MySQL's best practice is BINARY(16) with UUID_TO_BIN(uuid, 1) to swap the time bytes for better locality.

Is UUID case-sensitive?

UUID values are case-insensitive by specification. F47AC10B-58CC-4372-A567-0E02B2C3D479 and f47ac10b-58cc-4372-a567-0e02b2c3d479 represent the same UUID. The convention is lowercase. If you receive a UUID in uppercase (common from macOS's uuidgen), normalize it with .toLowerCase() before storing or comparing.

Should I include hyphens when storing UUIDs?

The canonical UUID format includes hyphens. If you store as CHAR(36), include them. If you store as BINARY(16), the hyphens are stripped during conversion. Never store without hyphens in a CHAR(32) column - this removes the visual grouping that makes UUIDs recognizable and may cause compatibility issues with libraries that expect the standard format.

How do I validate that a string is a valid UUID?

Use a regex that matches the standard format. For any UUID version:

// JavaScript
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
UUID_REGEX.test('f47ac10b-58cc-4372-a567-0e02b2c3d479'); // true
UUID_REGEX.test('not-a-uuid');                            // false

// For v4 specifically:
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;

What is the nil UUID?

The nil UUID is a special-case value where all bits are zero: 00000000-0000-0000-0000-000000000000. It is defined in the RFC and used as a sentinel value to represent "no UUID" or "unset" - similar to null for UUIDs. The max UUID (all bits set to 1, ffffffff-ffff-ffff-ffff-ffffffffffff) was added in RFC 9562 as another sentinel value.

Use our free tool to generate UUIDs instantly in your browser: UUID Generator →

UK
Written by Usman Khan
DevOps Engineer | MSc Cybersecurity | CEH | AWS Solutions Architect

Usman has 10+ years of experience securing enterprise infrastructure, managing high-traffic servers, and building zero-knowledge security tools. Read more about the author.