What Is a UUID?
A UUID (Universally Unique Identifier) is a 128-bit identifier designed to be unique across space and time — without requiring a central authority to coordinate assignments. The result is a 36-character string in the format:
550e8400-e29b-41d4-a716-446655440000
That string encodes 32 hexadecimal digits arranged in five groups separated by hyphens: 8-4-4-4-12. Two specific bit positions carry version and variant information, while the rest encode the identifier's actual data.
UUIDs are pronounced "you-id" or spelled out "U-U-I-D". You may also see the term GUID (Globally Unique Identifier) — that's Microsoft's name for the same standard (RFC 4122).
UUID Versions Explained
The UUID specification defines several versions, each using a different strategy to generate the 128-bit value. The version number is embedded in the identifier itself (the first digit of the third group).
UUID v1 — Time-Based + MAC Address
v1 combines the current timestamp (100-nanosecond intervals since 15 October 1582) with the network interface's MAC address. This makes it monotonically ordered by creation time on a single machine.
Drawbacks: It leaks the host machine's MAC address and the exact creation timestamp. This is a privacy concern — an attacker can determine when and on which machine an ID was created. Avoid v1 in user-facing contexts.
UUID v3 — Namespace + MD5
v3 is deterministic: the same input always produces the same UUID. It hashes a namespace UUID (e.g., the DNS namespace) with a name string using MD5. Useful when you need a reproducible UUID from a known input.
Prefer v5 over v3 — MD5 is considered cryptographically broken.
UUID v4 — Random (Most Common)
v4 fills 122 bits with cryptographically secure random data (the remaining 6 bits are reserved for version and variant). It is the most widely used version in modern software because it requires no state, no network, and no coordination.
// Example v4 UUIDs
f47ac10b-58cc-4372-a567-0e02b2c3d479
6ba7b810-9dad-11d1-80b4-00c04fd430c8
UUID v5 — Namespace + SHA-1
Like v3 but uses SHA-1 instead of MD5. Deterministic: same namespace + name = same UUID every time. Use this when you need to derive a UUID from a canonical source (e.g., turning a URL into a stable UUID).
UUID v6 and v7 — Time-Ordered (New Standard)
v6 and v7 are newer drafts (RFC 9562, published 2024) that fix the main performance weakness of older time-based UUIDs.
- v6: Reorders the timestamp fields so that UUIDs sort chronologically. Compatible with v1.
- v7: Uses a Unix millisecond timestamp in the most significant bits, followed by random data. This makes UUIDs naturally sortable and dramatically improves B-tree index performance in databases.
Why Use UUIDs?
Auto-incrementing integers were the default choice for decades. UUIDs solve problems that integers cannot:
- No central coordination: Any client, microservice, or edge device can generate a UUID independently. There is no "next ID" to request from a central database.
- Security through opacity: Sequential integer IDs expose your data volume. An attacker who sees
user/1042knows you have at least 1042 users. UUIDs reveal nothing. - Database merging: Merging two databases with integer IDs causes conflicts. UUID-keyed tables merge without collisions.
- Offline generation: Mobile apps, local-first software, and edge devices can create records offline and sync later — UUIDs guarantee no conflicts at sync time.
- Industry standard: UUID support is built into every major database, ORM, and API framework. There is no library to add.
UUID vs Other ID Types
| ID Type | Length | Sortable | Coordination | Notes |
|---|---|---|---|---|
| Auto-increment int | 4–8 bytes | Yes | Required | Exposes record count; breaks across distributed DBs |
| UUID v4 | 16 bytes / 36 chars | No | None | Universal support; random ordering hurts DB indexes |
| UUID v7 | 16 bytes / 36 chars | Yes (ms) | None | Best of both worlds for DB primary keys |
| ULID | 26 chars | Yes (ms) | None | Shorter, URL-safe; not an official standard |
| NanoID | 21 chars (default) | No | None | Shorter URL-safe IDs; configurable alphabet |
| CUID2 | ~24 chars | Partial | None | Collision resistant; not a formal standard |
NanoID and ULID are excellent choices for URL slugs where brevity matters. UUID remains the standard for database primary keys and inter-service identifiers because of its universal native support.
When NOT to Use UUIDs
UUIDs are not always the right tool. Be aware of these trade-offs:
- Storage overhead: A UUID stored as a string takes 36 bytes. An integer primary key takes 4–8 bytes. At millions of rows, this adds up — especially for index and foreign key storage. Mitigate by storing UUIDs as
BINARY(16)in MySQL or using the nativeUUIDtype in PostgreSQL. - Human readability: UUIDs make terrible URL slugs (
/post/f47ac10b-58cc-4372-a567-0e02b2c3d479is not memorable or shareable). Use slugs or short codes for user-facing URLs. - Index fragmentation with v4: Random v4 UUIDs insert at random positions in B-tree indexes, causing page splits and cache misses. For high-write tables that are indexed by their primary key, use UUID v7 or ULID instead.
- Legacy system compatibility: Some older systems assume integer primary keys. Check ORM and framework support before switching.
VARCHAR(36) in a high-volume table. Use BINARY(16) with UUID_TO_BIN(uuid, 1) to store them compactly and in time-ordered fashion. PostgreSQL has a native uuid column type that stores 16 bytes natively.
How to Generate UUIDs in Code
Every major language and database has built-in or first-party UUID support.
JavaScript / TypeScript
// Modern browsers and Node.js 14.17+
const id = crypto.randomUUID();
// "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
// For v7 (time-ordered), use the uuid package:
import { v7 as uuidv7 } from "uuid";
const id = uuidv7();
// "018e8b9b-2fb0-7000-8000-000000000000"
Python
import uuid
# v4 (random)
id_v4 = str(uuid.uuid4())
print(id_v4) # "f47ac10b-58cc-4372-a567-0e02b2c3d479"
# v5 (deterministic from namespace + name)
id_v5 = str(uuid.uuid5(uuid.NAMESPACE_DNS, "example.com"))
print(id_v5) # always the same for "example.com"
PHP
// PHP 8.x — using ramsey/uuid (composer require ramsey/uuid)
use Ramsey\Uuid\Uuid;
$v4 = Uuid::uuid4()->toString();
// "f47ac10b-58cc-4372-a567-0e02b2c3d479"
$v7 = Uuid::uuid7()->toString();
// "018e8b9b-2fb0-7000-8000-000000000000"
// Without a library (v4 only, PHP 5.3+):
function uuid4(): string {
$data = random_bytes(16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40); // version 4
$data[8] = chr(ord($data[8]) & 0x3f | 0x80); // variant bits
return vsprintf("%s%s-%s-%s-%s-%s%s%s", str_split(bin2hex($data), 4));
}
SQL
-- MySQL 8.0+
SELECT UUID(); -- v1 (time-based)
SELECT UUID_TO_BIN(UUID(), 1); -- binary(16), swap=1 for time ordering
-- PostgreSQL 13+
SELECT gen_random_uuid(); -- v4
-- PostgreSQL 16+ with pgcrypto
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
SELECT gen_random_uuid();
-- SQL Server
SELECT NEWID(); -- v4
SELECT NEWSEQUENTIALID(); -- sequential (good for indexes)
UUID Best Practices
- Store as binary, not string: Use
BINARY(16)in MySQL or nativeuuidin PostgreSQL. Never useVARCHAR(36)for primary keys. - Use v4 for most cases: Stateless, no coordination, universally supported. The right default.
- Use v7 for database primary keys: Time-ordered insertion preserves B-tree index performance. A major improvement over v4 for high-write workloads.
- Avoid v1 in privacy-sensitive contexts: It embeds your server's MAC address and the exact creation timestamp in the ID.
- Validate UUID format on input: Never assume a UUID string is valid. Use the regex below before inserting into your database.
- Use lowercase consistently: The spec is case-insensitive, but lowercase is the universal convention. Normalize on input.
- Don't use UUIDs as URL slugs: They are too long and non-memorable. Use a separate slug column for human-facing URLs.
How to Validate a UUID
A valid UUID matches this regular expression (case-insensitive):
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
// JavaScript
function isValidUUID(str) {
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str);
}
// Python
import re
def is_valid_uuid(s):
pattern = r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
return bool(re.match(pattern, s, re.IGNORECASE))
To also validate the version, check that the first character of the third group matches the expected version digit (e.g., 4 for v4, 7 for v7).
Frequently Asked Questions
Can two UUIDs ever be the same?
Theoretically yes — they are probabilistic, not mathematically guaranteed to be unique. In practice, the collision probability for v4 is approximately 1 in 5.3×1036. You will not encounter a collision in any real-world system. The sun will exhaust its fuel before you hit a UUID collision at any realistic generation rate.
UUID v4 vs v7 — which should I use for database primary keys?
v7 for database primary keys, v4 for everything else. v4 inserts at random positions in a B-tree index, causing page splits and degrading performance at scale. v7's millisecond-precision timestamp prefix ensures new records are always inserted near the end of the index — the same behavior as an auto-increment integer, but with no coordination required.
Are UUIDs case-sensitive?
No. The UUID specification defines them as case-insensitive, so f47ac10b-58cc-4372-a567-0e02b2c3d479 and F47AC10B-58CC-4372-A567-0E02B2C3D479 are the same identifier. By convention, always use lowercase.
What is the difference between UUID and GUID?
They are the same thing. GUID (Globally Unique Identifier) is Microsoft's term for the same RFC 4122 standard. The two terms are interchangeable in all technical contexts.
Can I use UUIDs in URLs?
Technically yes — all characters in a UUID are URL-safe (hex digits and hyphens). But they make for ugly, unmemorable URLs. For user-facing URLs prefer a human-readable slug. Reserve UUIDs for API endpoints and internal resource references where the URL is not meant to be shared or typed by hand.