What Is a UUID? A Complete Guide for Developers
The first time I encountered a UUID, I was debugging a payment processing pipeline at 1 AM. The error log showed Order 550e8400-e29b-41d4-a716-446655440000 failed to process. I spent five minutes trying to figure out which order number that was -- before realizing it was the order number.
If you've worked with APIs, databases, or distributed systems for any length of time, you've seen these 36-character strings. They show up in database records, REST responses, log files, cloud services, and event streams. They look random. They feel like over-engineered noise. And then you work on a system that has to merge data from five different services without central coordination, and suddenly the value of a globally unique identifier that any machine can generate independently becomes blindingly obvious.
This guide covers what UUIDs actually are, how each version works, when to use which one, and the mistakes I see developers make repeatedly in production.
What a UUID Actually Is
A UUID (Universally Unique Identifier) is a 128-bit value standardized by RFC 9562. It's designed so that two systems can generate identifiers independently -- without talking to each other, without a shared counter, without a central coordinator -- and still be confident the identifiers won't collide.
The canonical string representation uses 32 hex digits split by hyphens into an 8-4-4-4-12 pattern:
550e8400-e29b-41d4-a716-446655440000
│──────│ │──│ │──│ │──│ │──────────│
8 4 4 4 12
That string is just a human-readable encoding. Underneath, it's 128 bits of binary data. Different UUID versions use those 128 bits in different ways -- some embed timestamps, some embed randomness, some embed hashes of namespaced data.
Why UUIDs Exist: The Distributed ID Problem
Picture this: you're building a SaaS product. You start with one server and one PostgreSQL database. Auto-increment IDs work perfectly:
users table: 1, 2, 3, 4, 5...
Then you add a second application server. Then a read replica. Then a second region. Then an event-driven worker that creates records asynchronously. Each component creates records independently.
With auto-increment IDs, you run into the same collision every time:
Server A: users 1, 2, 3
Server B: users 1, 2, 3
-- Merge them and you have duplicate primary keys
UUIDs solve this at the architectural level. Every component generates its own IDs. No central sequencer. No coordination protocol. No collision.
Server A: 550e8400-e29b-41d4-a716-446655440000
Server B: 36b8f84d-df4e-4d49-b662-bcde71a8764f
Server A: a7b9d210-2b44-4ef4-9ef3-3db2a8ec3b1f
Server B: f7a3c110-1d44-4ef4-b662-d0a7f72e93f4
This is why companies running multi-region architectures, event sourcing systems, and microservices reach for UUIDs. Not because they look cool. Because they remove an entire category of coordination problems.
The UUID Versions: What Each One Does
Not all UUIDs are created equal. The version number (digit 13 in the hex string) tells you how the ID was generated.
| Version | Method | Best For |
|---|---|---|
| UUID v1 | Timestamp + MAC address | Legacy systems, time-based sorting |
| UUID v3 | MD5(namespace + name) | Deterministic IDs from known inputs |
| UUID v4 | 122 random bits | General purpose, most common today |
| UUID v5 | SHA-1(namespace + name) | Deterministic IDs (stronger than v3) |
| UUID v7 | 48-bit timestamp + 74 random bits | Database primary keys, time-ordered inserts |
In 2026, the two versions you'll actually choose between are v4 and v7. UUID v1 exposes hardware information (MAC addresses) which is a privacy concern. UUID v3 and v5 are deterministic -- you use them when you need the same UUID for the same input every time, like mapping user emails to IDs.
UUID v4: Pure Randomness
UUID v4 generates 122 bits of randomness. That's it. No timestamps, no machine IDs, no hashes.
// JavaScript (native, no dependencies)
const id = crypto.randomUUID();
// => "2d931510-d99f-494a-8c67-87feb05e1594"
import uuid
print(uuid.uuid4())
# => 2d931510-d99f-494a-8c67-87feb05e1594
UUID.randomUUID();
// => 2d931510-d99f-494a-8c67-87feb05e1594
The advantage is simplicity and privacy. No timestamp means no information leakage. The downside: randomness means random insertion order in database indexes, which causes page splits and fragmentation at scale. I'll get into that more in the section on UUID v4 vs v7.
UUID v7: Time-Ordered for Databases
UUID v7 puts a 48-bit Unix timestamp (millisecond precision) in the first segment, followed by 74 random bits for uniqueness within the same millisecond.
01911d4b-4c3f-7c21-ae6b-d0a7f72e93f4
│──────│
timestamp
+ random suffix
This means newer UUIDs are lexicographically larger than older ones. Database B-tree indexes love this -- new records append to the end of the index instead of landing in random positions. The insert performance difference between v4 and v7 on a large table can be 2-3x.
UUID v7 doesn't expose your MAC address (unlike v1). It preserves privacy while giving you time-based ordering. For new projects using UUIDs as primary keys in PostgreSQL or MySQL, v7 should be your default.
How UUIDs Work Internally
Every UUID carries metadata embedded in its bits. If you look at position 13 (the first character of the third group), you'll see the version:
550e8400-e29b-41d4-a716-446655440000
↑
'4' = UUID v4
For UUID v7, that character would be a 7:
01911d4b-4c3f-7c21-ae6b-d0a7f72e93f4
↑
'7' = UUID v7
The next character (position 14, first char of the fourth group) encodes the variant. For RFC-compliant UUIDs, this is 8, 9, a, or b.
These version and variant bits consume a small portion of the 128-bit space, which is why UUID v4 has 122 random bits instead of the full 128.
Common Use Cases
Database Primary Keys
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
customer_id UUID NOT NULL,
created_at TIMESTAMPTZ DEFAULT now()
);
PostgreSQL has native UUID type support. MySQL 8.0+ supports BINARY(16) for compact UUID storage with functions like UUID_TO_BIN() and BIN_TO_UUID(). Storing as the native type instead of TEXT saves about 20 bytes per row and speeds up index lookups.
REST API Resource IDs
Instead of exposing sequential integer IDs (which let anyone enumerate your resources):
GET /api/users/123 -- anyone can guess /api/users/124
Use UUIDs:
GET /api/users/550e8400-e29b-41d4-a716-446655440000
UUIDs aren't a security mechanism -- you still need proper authentication and authorization. But they make resource enumeration significantly harder. If you're designing APIs, also consider reading about JSON parse errors in API responses -- another common API debugging pain point.
Event Tracking and Message Queues
When an event travels through multiple services (API → queue → worker → database), attaching a UUID as the correlation ID lets you trace it through every hop:
{
"eventId": "550e8400-e29b-41d4-a716-446655440000",
"type": "order.created",
"payload": { "orderId": "36b8f84d-df4e-4d49-b662-bcde71a8764f" }
}
File Storage
Cloud object storage (S3, GCS) commonly uses UUIDs for object keys:
uploads/550e8400-e29b-41d4-a716-446655440000/profile.jpg
No naming conflicts, no race conditions, no coordination needed between upload workers.
UUID Validation: The Right Way
You'll often need to check if a user-provided string is a valid UUID. The most reliable approach uses the language's built-in UUID parser rather than regex:
function isValidUUID(str) {
const pattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
return pattern.test(str);
}
But regex can miss edge cases around variant bits. A parser-based approach is more robust:
import uuid
def is_valid_uuid(value):
try:
uuid.UUID(value)
return True
except (ValueError, AttributeError):
return False
For quick validation during development, the UUID generator tool includes a built-in validator that checks version, variant, and formatting in one pass.
Common Mistakes Developers Make
Storing UUIDs as TEXT
This is the most common production mistake I see:
-- Wrong
id TEXT PRIMARY KEY
-- Right
id UUID PRIMARY KEY -- PostgreSQL
id BINARY(16) PRIMARY KEY -- MySQL
The TEXT representation takes 36 bytes per ID. The native binary representation takes 16 bytes. Over 10 million rows, that's 200MB of wasted storage just for the ID column -- plus the extra index size and slower comparison operations.
Using Math.random() Instead of crypto.randomUUID()
If you write a custom UUID generator using Math.random(), you're creating IDs that are neither unique nor cryptographically random. The Math.random() PRNG is predictable with a small number of observed outputs:
// Never do this
function badUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
const r = Math.random() * 16 | 0; // NOT cryptographically secure
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
}
// Always use this
crypto.randomUUID();
Treating UUIDs as Auth Tokens
UUIDs identify resources. They're not passwords. A UUID in a URL tells you which resource to fetch, not whether you're allowed to fetch it. Authentication and authorization are separate concerns.
Ignoring UUID Version in Database Indexes
If you're using UUID v4 as a primary key on a write-heavy table, monitor your index fragmentation. After millions of inserts, random UUIDs can cause B-tree page splits that degrade insert performance. Check your database's index statistics periodically. The UUID v4 vs v7 comparison covers this in detail.
UUIDs vs Other ID Strategies
Nano ID
Nano ID generates shorter, URL-friendly identifiers using a configurable alphabet. At 21 characters with the default alphabet, it provides ~126 bits of entropy -- slightly more than UUID v4's 122 bits:
import { nanoid } from 'nanoid';
const id = nanoid(); // => "V1StGXR8_Z5jdHi6B-myT"
Nano ID is more compact (21 chars vs 36) and URL-safe by default (no hyphens). For new projects where you control the full stack, it's worth considering. For projects that need interoperability with existing UUID systems (databases, third-party APIs, libraries), UUID v4 or v7 is the safer default. Our random ID generator lets you experiment with both formats side by side.
ULID
ULID provides 26-character time-ordered identifiers with a Crockford base32 encoding:
01ARZ3NDEKTSV4RRFFQ69G5FAV
ULID was designed to solve the same index fragmentation problem that UUID v7 addresses. UUID v7 has the advantage of fitting into existing UUID infrastructure -- same column type, same libraries, same format. ULID is more human-readable but requires special handling in most databases.
Snowflake IDs
Twitter's Snowflake generates 64-bit time-ordered IDs using a combination of timestamp, worker ID, and sequence number. They unpack to roughly 8 bytes -- half the size of a UUID. The tradeoff is that they require worker coordination (each worker needs a unique ID) and aren't standards-based.
FAQ
What does UUID stand for?
Universally Unique Identifier. The term is defined in RFC 9562 (which obsoleted the earlier RFC 4122).
How many possible UUIDs are there?
The full 128-bit space allows 2^128 ≈ 3.4 × 10^38 possible values. UUID v4 uses 122 random bits, giving 2^122 ≈ 5.3 × 10^36 possibilities. For context on how large that number is, read Can UUIDs Really Collide?.
Which UUID version should I use in 2026?
For new projects using UUIDs as database primary keys: UUID v7. For general-purpose identifiers where ordering doesn't matter: UUID v4. For deterministic generation from known inputs: UUID v5.
Is UUID v7 faster than v4 in databases?
Yes, significantly. UUID v7's time-ordered structure means new records append to the end of B-tree indexes, avoiding the random page splits that UUID v4 causes. The performance difference grows with table size.
Can I use UUIDs as primary keys in MySQL?
Yes. Use BINARY(16) for storage and the UUID_TO_BIN() / BIN_TO_UUID() functions for conversion. MySQL's InnoDB engine benefits particularly from time-ordered UUIDs (v7) due to its clustered index structure.
How do I generate a UUID in JavaScript?
crypto.randomUUID() is built into modern browsers, Node.js 19+, and all major edge runtimes. No dependencies needed. It generates UUID v4.
Are online UUID generators safe?
Depends on the implementation. Tools that process data server-side may log generated values. Our UUID generator uses crypto.getRandomValues() and runs entirely in the browser -- nothing is sent to a server. Generated UUIDs stay on your machine.
How do UUIDs compare to auto-increment IDs?
UUIDs are larger (16 bytes vs 8 bytes for BIGINT), don't sort naturally (except v7), and are harder to read. But they're globally unique, work across distributed systems, and don't expose record counts. For the full comparison, see UUID vs Auto-Increment IDs.
If you're designing a new system and need to experiment with UUID formats before committing to an ID strategy, the UUID generator lets you generate UUID v1, v4, and v7 in bulk -- with format options like hex, uppercase, and JSON array output. It runs locally in your browser and supports batch generation up to 100 UUIDs at once, which is handy for seeding test databases or verifying collision behavior in your code.