The Ultimate Guide to UUIDs
Everything developers need to know about UUIDs, including v4 vs v7, use cases, and best practices.
What Is a UUID?
A Universally Unique Identifier (UUID) is a 128-bit label used to uniquely identify information in computer systems. The standard format, defined by RFC 4122 and recently updated by RFC 9562, displays as 32 hexadecimal digits split into five groups separated by hyphens: 8-4-4-4-12. A typical UUID looks like this: `f47ac10b-58cc-4372-a567-0e02b2c3d479`. With 128 bits of entropy, the total number of possible UUIDs is roughly 5.3 x 10^36 — a number so vast that if you generated one billion UUIDs every second for a century, you'd still only have used a tiny fraction of the available space. This astronomical namespace is what makes UUIDs the go-to choice for systems where unique identifiers must be generated independently across distributed nodes without central coordination.
UUID Versions at a Glance
Not all UUIDs are created equal. The specification defines multiple versions, each designed for different use cases and tradeoffs:
- **UUID v1 (Time-Based + MAC):** Generated from the current timestamp (100-nanosecond intervals since October 15, 1582) combined with the host machine's MAC address. It's deterministic and traceable, which makes it useful for debugging but a privacy concern since the MAC address can identify the generating machine.
- **UUID v3 (MD5 Hash):** Derives the UUID by hashing a namespace identifier and a name using MD5. Two identical inputs always produce the same UUID, making v3 suitable for generating deterministic IDs from known strings.
- **UUID v4 (Random):** The most widely used version. Almost all 128 bits are randomly generated (122 random bits + 6 fixed variant/version bits). It has no inherent ordering and offers maximum unpredictability.
- **UUID v5 (SHA-1 Hash):** Same concept as v3 but uses the stronger SHA-1 hash algorithm. Prefer v5 over v3 for new deterministic UUID schemes.
- **UUID v7 (Time-Ordered Random):** The newest standard. Encodes a Unix timestamp in milliseconds as the leading bits, followed by random bits. This gives v7 the uniqueness of random UUIDs but with a monotonic time-sorted prefix.
UUID v4 Deep Dive
UUID v4 is the workhorse of modern application development. Its structure breaks down as: 6 fixed bits for the version (0100) and variant (10), leaving 122 bits of pure randomness. This produces 2^122 — approximately 5.3 x 10^36 — distinct values. The collision probability is the classic birthday problem: with 2^122 random UUIDs, you'd need to generate about 2^61 (roughly 2.3 quintillion) before reaching a 50% chance of collision. To put that in perspective, if you generated 1 billion UUIDs per second, it would take about 73,000 years to hit that threshold. For any practical system, the chance of a UUID v4 collision is essentially zero. You can safely generate UUIDs on any node, at any time, and treat them as globally unique without checking against a central registry.
UUID v7: The Modern Choice
UUID v7 addresses the one significant weakness of v4: database index performance. A v7 UUID encodes a 48-bit Unix timestamp (milliseconds since epoch) as the most significant bits, followed by 74 random bits (with the usual 6 fixed variant/version bits). The result is monotonically increasing over time (within the same millisecond, ordering is random). This time-prefixed structure is a game-changer for B-tree indexes — the default index structure in nearly every relational database. B-trees perform optimally when new insertions have keys that are strictly increasing or nearly so. Random v4 UUIDs cause index page splits, cache churn, and fragmentation because each new insertion lands at a random position in the tree. With v7, new rows always append near the end of the index, dramatically reducing page splits and improving write throughput. For any new system using UUIDs as primary keys in a database, v7 is almost always the right choice.
When to Use UUIDs
UUIDs shine in distributed and decentralized architectures. Use them when:
- **Distributed Systems:** Multiple services, microservices, or edge nodes generate identifiers independently without a central ID server. UUIDs eliminate coordination overhead and single points of failure.
- **Public API Identifiers:** Exposing sequential integer IDs in URLs is a common security anti-pattern — it lets anyone enumerate your resources. UUIDs are opaque and unguessable, preventing information leakage.
- **Offline-First Applications:** Mobile and desktop apps that generate data while offline need to synchronize later. UUIDs guarantee no conflicts when merging data, since each client generates IDs that are globally unique.
- **Event Sourcing and CQRS:** Event streams require immutable, globally unique event IDs. UUIDs, particularly v7 with their time component, provide both uniqueness and chronological ordering of events.
When NOT to Use UUIDs
UUIDs are not a universal hammer. Avoid them when:
- **Short URLs:** Services like YouTube, Bitly, or Twitter use compact encoded identifiers (often 7-11 characters) because they're shared in constrained contexts. A 36-character UUID is far too long.
- **Sequential Display IDs:** Invoices numbered `INV-0001` through `INV-9999` communicate order and scale to customers. A UUID like `550e8400-e29b-41d4-a716-446655440000` is user-hostile for human-facing sequences.
- **Human-Readable Identifiers:** If humans need to read, type, or communicate identifiers verbally (order numbers, ticket IDs, booking references), use shorter, character-set-constrained formats like `ABC-XYZ-123` rather than raw UUIDs.
Database Performance: UUID v4 vs. BIGSERIAL vs. UUID v7
The choice of primary key type has profound performance implications in relational databases:
**BIGSERIAL:** A monotonically increasing 64-bit integer. Ideal for B-tree performance — new rows always insert at the rightmost leaf. It is the fastest option for write-heavy workloads. The tradeoff: it requires a central sequence generator, it's predictable, and it leaks information about data volume and growth rate.
**UUID v4 (Random):** Each insertion lands at a random position in the B-tree. This causes frequent page splits (a page fills up and the database must split it into two, rebalancing the tree), wasted space (pages end up partially filled — as low as 50-60% fill factor), and increased cache pressure (random access pattern evicts hot pages). Write throughput can degrade by 30-50% compared to sequential keys.
**UUID v7 (Time-Ordered):** Combines the distributed uniqueness of UUIDs with near-sequential insertion behavior. The timestamp prefix ensures new keys are monotonically increasing most of the time. Page splits are rare, fill factor stays high, and cache efficiency approaches that of BIGSERIAL. Benchmarks consistently show v7 performing within 5-10% of sequential integers while offering global uniqueness without coordination.
If your workload is read-dominated and cache-friendly, the performance difference between v4 and v7 may not matter. If you're writing millions of rows, the difference is dramatic.
UUID Alternatives
The identifier landscape has expanded well beyond the original UUID standard:
- **ULID (Universally Unique Lexicographically Sortable Identifier):** A 128-bit identifier with a 48-bit Unix timestamp (second precision) and 80 bits of randomness, encoded as a 26-character Crockford Base32 string. ULIDs are case-insensitive, URL-safe, and lexicographically sortable. Unlike UUID v7, ULID is not an official standard, but it predates and inspired v7.
- **Snowflake ID (Twitter/X):** A 64-bit integer composed of a timestamp, datacenter ID, worker ID, and sequence number. Extremely compact and sortable, but it requires a centralized service or configuration to assign unique worker IDs. At 64 bits, the collision space is much smaller — designed for a single organization's internal use.
- **NanoID:** A URL-safe, configurable-length ID generator. Default NanoID (21 characters) offers ~126 bits of entropy from a 64-character alphabet. Its main advantage is compactness — you can trade collision probability for length (e.g., 8-character IDs for short-lived identifiers). NanoID is a library, not a standard, and has no built-in time-ordered component.
- **KSUID (K-Sortable Unique Identifier):** A 128-bit ID with a 32-bit timestamp (second precision) and 96 bits of random payload, encoded in Base62 (27 characters). KSUIDs are sortable by time within the same second and offer a larger random space than ULID.
Each trades off between compactness, sortability, decentralized generation, and collision resistance. Choose based on your specific constraints: Snowflake for ultra-compact internal IDs at scale, ULID/KSUID for sortable human-readable IDs, NanoID for short tokens, and UUID v7 for standards-compliant interoperable identifiers.
Implementation Guide
Generating UUIDs in practice is straightforward across modern platforms:
- **Our UUID Generator Tool:** The companion tool at `/uuid` provides instant generation of all UUID versions with bulk export and format options.
- **Browser (JavaScript):** `crypto.randomUUID()` is now supported in all modern browsers and generates UUID v4 natively. For v7 support, use the `uuid` package or the new `crypto.randomUUID({ version: 7 })` API available in recent Chromium builds.
- **Node.js:** The `uuid` package (npm install uuid) provides `uuid.v4()` and `uuid.v7()` out of the box. Use `uuid.v7()` for new projects. It's well-tested, maintained, and handles edge cases like clock regression.
- **Python:** The standard library `uuid` module offers `uuid.uuid4()` and `uuid.uuid7()` (Python 3.14+). For earlier versions, the `uuid7` package provides v7 generation.
- **Java:** `java.util.UUID.randomUUID()` gives v4. For v7, use the `uuid-creator` library or the new `java.util.UUID.randomUUID(7)` in JDK 21+.
- **Go:** The `github.com/google/uuid` package supports both v4 (`uuid.New()`) and v7 (`uuid.Must(uuid.NewV7())`).
- **Rust:** The `uuid` crate with the `v7` feature provides `Uuid::now_v7()`.
Best Practices
Follow these guidelines to get the most out of UUIDs in production:
- **Store as BINARY(16) in MySQL:** Never store UUIDs as CHAR(36) — 16 bytes vs 36 bytes is a 2.25x storage saving, plus binary comparison is faster than string comparison. Use `UUID_TO_BIN()` and `BIN_TO_UUID()` functions. In MySQL 8.0+, consider `UUID v7` with `BINARY(16)` for optimal index performance.
- **Use the UUID Type in PostgreSQL:** PostgreSQL has a native `uuid` data type that stores the 128-bit value efficiently. For v7, store the timestamp and random parts separately or use an extension. PostgreSQL's B-tree handling of UUIDs benefits significantly from v7 ordering.
- **Index UUID Columns Judiciously:** If you query by UUID frequently, index it — but prefer clustered indexes on a sequential surrogate key with a UUID as a secondary indexed column for write-heavy tables. Some databases (SQL Server, MySQL with InnoDB) cluster the table by the primary key; a random clustered key causes severe fragmentation.
- **Never Truncate UUIDs:** It is tempting to take the first 8 or 16 characters of a UUID for a "short ID." This is dangerous. Truncating a UUID drastically reduces entropy and geometrically increases collision probability. Eight hex characters is only 32 bits — you'll likely see collisions after just 77,000 generated IDs (birthday problem). If you need short IDs, use a purpose-built scheme like NanoID or base-62 encoding of a larger random value.
- **Make UUID v7 the Default:** For all new projects that need distributed unique identifiers, start with UUID v7. It provides the interoperability and standardization of UUIDs with the database performance characteristics of sequential keys. The only reason to choose v4 over v7 is if you absolutely must avoid encoding any timestamp information — a valid privacy or security concern in some contexts.