Three microservices are creating order records at the same time. Each service runs its own database. You need a way to generate order IDs without collisions — and without a central server handing out sequential numbers. That central server becomes a bottleneck, and if it goes down, your entire system stops accepting orders. This exact problem is why UUIDs exist.
A UUID (Universally Unique Identifier) is a 128-bit identifier that any system can generate independently, with no coordination required. You have almost certainly seen strings like 550e8400-e29b-41d4-a716-446655440000 in logs, database records, or API responses. This guide covers everything a working developer needs to know about UUIDs — from internal structure to production patterns.
UUID Structure and Version Differences (v1–v7)
A UUID consists of 32 hexadecimal characters separated into five groups by hyphens: xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx. The M position indicates the version number, and the upper bits of N encode the variant. Out of 128 total bits, 4 are reserved for the version and 2 for the variant, leaving 122 bits for actual identification.
Those 122 bits produce 5.3 x 10^36 possible values — more than the estimated number of grains of sand on Earth. The original versions were defined in RFC 4122, while newer versions (v6 and v7) were standardized in RFC 9562, approved in 2024.
Here is a comparison of all UUID versions:
| Version | Generation Method | Key Characteristics | Usage Frequency |
|---|---|---|---|
| v1 | Timestamp + MAC address | Time-ordered, but exposes hardware MAC address | Low |
| v2 | Timestamp + POSIX UID/GID | Designed for DCE security; rarely used in practice | Very low |
| v3 | MD5 hash (namespace + name) | Deterministic — same input always produces same UUID | Occasional |
| v4 | Random (CSPRNG) | Simple, secure, no external dependencies | Very high |
| v5 | SHA-1 hash (namespace + name) | Same concept as v3 but with a stronger hash | Occasional |
| v6 | Timestamp (v1 rearranged) | Fixes v1's bit ordering for better sortability | Growing |
| v7 | Unix timestamp + random | Time-ordered with randomness; database-friendly | Growing fast |
Versions 3 and 5 are deterministic. Feed them the same namespace and name, and they always return the same UUID. This is useful when you need to convert a known value — like a URL or email address — into a stable UUID without storing a mapping.
Versions 6 and 7 are the newer additions. Version 7 is particularly interesting because it places a millisecond-precision Unix timestamp in the most significant bits. This means UUIDs sort chronologically by default, which dramatically improves B-Tree index insertion performance. If you are starting a new project and need time-ordered identifiers, v7 deserves serious consideration.
Why UUID v4 Is the Most Popular Choice
UUID v4 fills its 122 available bits with cryptographically secure random data (CSPRNG). The implementation is trivial, it exposes zero system information (unlike v1's MAC address leakage), and it requires no external dependencies. No network connection, no clock synchronization, no shared state — you can generate a v4 UUID anywhere.
Every major programming language ships with a built-in way to create v4 UUIDs. Java has UUID.randomUUID(), Python has uuid.uuid4(), and modern JavaScript has crypto.randomUUID(). There is nothing to configure, nothing to initialize, and nothing to import beyond the standard library. That simplicity is exactly why v4 dominates.
The tradeoff is ordering. Since v4 is fully random, there is no correlation between generation time and sort order. When you use v4 UUIDs as primary keys in PostgreSQL or MySQL, each insert lands at a random position in the B-Tree index. This causes frequent page splits. For tables under a few million rows, you probably will not notice. Once you cross tens of millions of rows, the insertion performance gap becomes measurable.
If that concerns you, UUID v7 solves this problem. It preserves time ordering while maintaining enough randomness for uniqueness. However, library support for v7 is still catching up to v4's ubiquity, so evaluate based on your project's requirements and available tooling.
UUID vs Auto-Increment ID — When to Use Which
Choosing between UUIDs and auto-increment integers for primary keys is one of the most common database design debates. The answer depends on your architecture, and the tradeoffs are concrete.
Auto-increment IDs work well when:
- You run a single database on a single server
- JOIN-heavy queries need maximum performance (4-byte INT vs. 16-byte UUID matters at scale)
- Developers need to read IDs during debugging and quickly understand ordering
- Your data volume will never exceed the INT range (~2.1 billion rows) or BIGINT range
UUIDs work well when:
- Multiple nodes in a distributed system generate IDs independently
- Clients create data offline and sync later (mobile apps, field devices)
- Sequential IDs in URLs would leak information (e.g.,
/users/3reveals your total user count) - You need to merge data from multiple databases without ID conflicts
A common production pattern combines both approaches. Use an auto-increment BIGINT as the internal primary key for fast JOINs, and add a UUID column as the public-facing identifier exposed through your API.
CREATE TABLE orders (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
public_id CHAR(36) NOT NULL UNIQUE DEFAULT (UUID()),
user_id BIGINT NOT NULL,
total_amount DECIMAL(10, 2),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- API consumers query by public_id
SELECT * FROM orders WHERE public_id = '550e8400-e29b-41d4-a716-446655440000';
This hybrid approach gives you the JOIN performance of integer keys and the security and flexibility of UUIDs for external communication.
UUID Collision Probability: Should You Actually Worry?
The short answer: no. The math makes this unambiguous.
UUID v4 has 122 bits of randomness. Using the Birthday Problem formula, you would need to generate approximately 2.71 x 10^18 UUIDs before the probability of a single collision reaches 50%. To put that in perspective: if you generated one billion UUIDs per second, it would take roughly 86 years to reach a 50% collision probability. At a more realistic rate of one million per second, you are looking at 86,000 years.
A UUID v4 collision is less likely than being struck by a meteorite. Adding a UNIQUE constraint to your UUID column in the database is sensible defensive programming, but treating UUID collisions as a realistic risk in your system design is not.
There is one critical caveat. These probability numbers assume a proper CSPRNG (Cryptographically Secure Pseudo-Random Number Generator) as the randomness source. If you generate UUIDs using a weak random source like JavaScript's Math.random(), the collision probability skyrockets because the entropy pool is far smaller. Always use your language's official UUID library or its crypto module. Never roll your own randomness for UUID generation.
How to Generate UUIDs in Different Programming Languages
The code for generating a UUID v4 varies slightly by language. Here are practical examples for the most common ones.
JavaScript / TypeScript (Node.js and Browser)
// Built-in: works in browsers and Node.js 18+
const id = crypto.randomUUID();
// Result: "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed"
// Using the uuid package (Node.js)
import { v4 as uuidv4 } from 'uuid';
const id = uuidv4();
Python
import uuid
# v4 (random)
id = uuid.uuid4()
print(id) # 6ba7b810-9dad-41d4-80b9-7c0267015889
# v5 (SHA-1 based, deterministic)
id = uuid.uuid5(uuid.NAMESPACE_URL, "https://example.com")
Java
import java.util.UUID;
// Generate v4
UUID id = UUID.randomUUID();
System.out.println(id);
// Parse UUID from string
UUID parsed = UUID.fromString("550e8400-e29b-41d4-a716-446655440000");
Go
package main
import (
"fmt"
"github.com/google/uuid"
)
func main() {
id := uuid.New() // v4
fmt.Println(id)
}
If you need a UUID quickly without writing code — for test data, configuration files, or just verifying a format — the UUID Generator creates v4 UUIDs with a single click. It supports bulk generation and format options like removing hyphens.
UUID Patterns in Production (DB Primary Keys, API Keys, Distributed Systems)
Database Primary Keys
The biggest decision when using UUIDs as primary keys is the storage format. Storing as CHAR(36) wastes space — 36 bytes per row including hyphens. Switching to BINARY(16) cuts that to 16 bytes, which reduces your index size by more than half. That difference compounds across millions of rows and every index that references the PK.
PostgreSQL provides a native UUID type that stores 16 bytes internally, so no conversion is needed. MySQL requires a bit more work but offers the UUID_TO_BIN function.
-- PostgreSQL: native UUID type
CREATE TABLE sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
expires_at TIMESTAMPTZ NOT NULL
);
-- MySQL: BINARY(16) storage
CREATE TABLE sessions (
id BINARY(16) PRIMARY KEY DEFAULT (UUID_TO_BIN(UUID(), 1)),
user_id BINARY(16) NOT NULL,
expires_at DATETIME NOT NULL
);
The second argument 1 in MySQL's UUID_TO_BIN rearranges the timestamp fields to the front of the binary representation. This makes UUIDs sort more chronologically, reducing random page splits in the B-Tree index.
API Keys and Tokens
Using a raw UUID as an API key is a common mistake. While UUIDs are unique, they were not designed as secret tokens — 122 bits of entropy is below the recommended minimum for security-sensitive credentials. A better pattern uses UUID as the public identifier for the key, paired with a separate high-entropy secret.
// API key issuance pattern
const apiKeyId = crypto.randomUUID(); // Public identifier
const apiKeySecret = crypto.randomBytes(32).toString('hex'); // Secret token (256 bits)
// Store in DB: hash the secret before saving
const hashedSecret = await bcrypt.hash(apiKeySecret, 10);
await db.apiKeys.create({ id: apiKeyId, secretHash: hashedSecret });
The user sees both the ID and secret at creation time. On subsequent requests, you look up the key by its UUID and verify the secret against the stored hash. This separation means even if your database leaks, the actual secrets remain protected.
Distributed Systems
UUIDs prove their worth in microservice architectures. Each service generates identifiers independently, eliminating inter-service communication for ID assignment. Common patterns include: event IDs in event sourcing, transaction IDs in the Saga pattern, and idempotency keys for message queues and payment processing.
// Idempotency key: prevents duplicate processing
const idempotencyKey = crypto.randomUUID();
await fetch("/api/payments", {
method: "POST",
headers: { "Idempotency-Key": idempotencyKey },
body: JSON.stringify({ amount: 99.99, currency: "USD" })
});
Mobile apps benefit from UUIDs for offline-first architectures. When a user creates a note while in airplane mode, the client assigns a UUID immediately. Once connectivity returns, the note syncs to the server with its pre-assigned ID. If the server were responsible for ID assignment, offline data creation would be impossible.
For quick prototyping or when you need UUIDs for test fixtures and configuration, the UUID Generator handles bulk generation directly in the browser — no code or CLI required.
FAQ
Can I store UUIDs without hyphens?
Yes. Hyphens are purely a formatting convention for human readability — they are not part of the UUID value itself. 550e8400e29b41d4a716446655440000 and 550e8400-e29b-41d4-a716-446655440000 represent the same UUID. For database storage, BINARY(16) or a native UUID type is the most space-efficient option regardless of display format.
Can UUIDs be used as sortable IDs?
UUID v4 cannot be sorted by creation time because it is fully random. If you need time-based ordering, use UUID v7. Version 7 places a millisecond-precision Unix timestamp in the first 48 bits, so a simple lexicographic sort on the string representation produces chronological order. This also makes v7 significantly better for database index performance compared to v4.
Is it safe to expose UUIDs in URLs?
A UUID is not a secret. UUID v4 is effectively impossible to guess due to its randomness, which prevents enumeration attacks that plague sequential IDs. However, an unguessable identifier is not a substitute for proper access control. Your server must still verify authentication and authorization on every request. Think of UUIDs as "hard to guess" identifiers, not access tokens.
How many UUIDs can I generate before collisions become a real concern?
For UUID v4, you would need to generate approximately 2.71 quintillion (2.71 x 10^18) identifiers to reach a 50% collision probability. Most applications generate nowhere near that volume in their entire lifetime. The real risk is not mathematical collision — it is using a weak random number generator. Stick to your language's standard crypto module or official UUID library, and collisions will remain a theoretical rather than practical concern.
