What Is a UUID?

A UUID (Universally Unique Identifier) is a 128-bit label used to identify information in computer systems. Standardized in RFC 4122 (and updated by RFC 9562 in 2024), a UUID is typically written in this canonical form:

UUID Complete Guide: Versions, Use Cases, and Choosing Between v4 and v7

550e8400-e29b-41d4-a716-446655440000

Eight hex characters, then groups of four, four, four, and twelve — always 32 hex digits plus four hyphens, 36 characters total.

The fundamental promise of a UUID is that you can generate one on any machine at any time with an astronomically low probability of collision with any other UUID ever generated. This is what makes them invaluable in distributed systems where you cannot coordinate with a central ID authority.


The Complete Version Reference

The UUID specification defines multiple versions, each with a different generation strategy.

VersionGeneration StrategySortableWhen to Use
v1MAC address + timestampPartiallyLegacy systems, audit trails
v2DCE Security (POSIX UID/GID)NoAlmost never
v3MD5 hash of namespace + nameNoDeterministic IDs from known inputs (legacy)
v4RandomNoGeneral purpose, session IDs
v5SHA-1 hash of namespace + nameNoDeterministic IDs from known inputs
v6Timestamp + random (reordered v1)YesOrdered replacement for v1
v7Unix timestamp ms + randomYesDatabase primary keys (recommended)
v8Custom layoutVariesApplication-specific formats

Version 1: MAC Address + Timestamp

UUID v1 encodes the MAC address of the generating machine and a 100-nanosecond timestamp. The collision risk is extremely low because the combination of machine identity and time is unique.

The downside: MAC addresses are personally identifiable. Generating v1 UUIDs in a web application can inadvertently reveal server hardware details. For this reason, v1 has largely fallen out of favor for new systems.

Version 3 and Version 5: Deterministic UUIDs

Both v3 and v5 generate a UUID from a namespace UUID and a name string using a hash function (MD5 for v3, SHA-1 for v5). Given the same inputs, you always get the same UUID output.

This is useful for creating stable identifiers from external data:

// v5 example: same URL always produces the same UUID
// Namespace UUID for URLs is defined in RFC 4122
const DNS_NAMESPACE = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
uuidv5('example.com', DNS_NAMESPACE); // always the same result

Use v5 over v3 when possible — SHA-1 is stronger than MD5, though neither is suitable for cryptographic purposes in new designs.

Version 4: The Workhorse

UUID v4 is simply 122 bits of cryptographically random data (the remaining 6 bits encode the version and variant). It is the most widely generated UUID type in existence.

// Browser / Node.js (built-in, no library needed)
const id = crypto.randomUUID(); // "f47ac10b-58cc-4372-a567-0e02b2c3d479"

// Python
import uuid
print(uuid.uuid4())  # 3d6f4890-4f40-4d09-aa11-3efb...

// Go
import "github.com/google/uuid"
id := uuid.New()

Collision probability: If you generated one billion v4 UUIDs per second for 86 years, the probability of a single collision would be roughly 50%. For practical purposes, collisions are effectively impossible.

Limitation: v4 UUIDs are random, meaning they are not sortable. Inserting random UUIDs as primary keys in a B-tree index causes index fragmentation over time, which hurts write performance at scale.

Version 7: The Modern Standard

UUID v7 was standardized in RFC 9562 (May 2024). It places a Unix millisecond timestamp in the most significant bits, followed by random data.

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           unix_ts_ms                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          unix_ts_ms           |  ver  |       rand_a          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var|                        rand_b                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                            rand_b                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Because the timestamp is in the high bits, v7 UUIDs are lexicographically sortable by generation time. Two UUIDs generated in sequence will sort in that order — exactly what database indexes want.

// Using the 'uuidv7' package
import { uuidv7 } from 'uuidv7';

const id1 = uuidv7(); // "018e1f27-42a0-7000-8000-..."
const id2 = uuidv7(); // "018e1f27-42a1-7000-..."
// id1 < id2 (string comparison gives correct order)

UUID vs. Sequential Integer IDs

For database primary keys, the traditional choice was auto-incrementing integers (SERIAL, AUTO_INCREMENT). UUIDs change that trade-off.

ConcernAuto-increment IntegerUUID v4UUID v7
ID predictabilityGuessable (enumerable)UnpredictableUnpredictable
Index performanceExcellent (sequential)Poor (random writes)Excellent (time-ordered)
Distributed generationRequires central coordinatorAny node, any timeAny node, any time
Merge / replicationComplexSimpleSimple
Row size overhead4–8 bytes16 bytes16 bytes
Human readabilityEasyHardHard

For most modern systems — especially those running microservices, handling multi-region deployments, or merging data from multiple sources — UUID v7 is the right default for primary keys.


Generating UUIDs in Common Languages

JavaScript / TypeScript

// v4 — built into modern browsers and Node.js 14.17+
const sessionId = crypto.randomUUID();

// v7 — install uuidv7 package
import { uuidv7 } from 'uuidv7';
const recordId = uuidv7();

Python

import uuid

# v4
print(uuid.uuid4())

# v7 — available in Python 3.12+ via third-party lib
# pip install uuid7
from uuid7 import uuid7
print(uuid7())

Go

import "github.com/google/uuid"

// v4
id := uuid.New()

// v7
id, err := uuid.NewV7()

PostgreSQL

-- v4 built-in (requires pgcrypto or pg 13+)
SELECT gen_random_uuid();

-- v7 with pgx or application-side generation
INSERT INTO users (id, email) VALUES ($1, $2)
-- pass uuidv7() from application layer

Practical Considerations

Storing UUIDs in Databases

  • PostgreSQL: Use the native UUID type. Stores as 16 bytes, not 36 character strings.
  • MySQL/MariaDB: Use BINARY(16) for efficiency, or CHAR(36) for readability. Insert with UUID_TO_BIN(uuid, true) to preserve sortability.
  • SQLite: Store as BLOB(16) or TEXT(36).

String vs. Binary Storage

A UUID as a text string ("f47ac10b-58cc-4372-a567-0e02b2c3d479") occupies 36 bytes. As raw binary, it is 16 bytes. At millions of rows, that difference is meaningful both for storage and index size.

URL-Safe UUIDs

Standard UUIDs are URL-safe because they only contain hex digits and hyphens. However, if you want an even more compact representation for use in URLs, you can remove the hyphens (32 characters) or use Base64Url encoding (22 characters).


FAQ

Is UUID v4 truly random?

UUID v4 uses 122 bits of random data (the other 6 bits are fixed for version and variant). Whether it is “truly” random depends on the random number generator. Most modern environments use a cryptographically secure pseudorandom number generator (CSPRNG), which is suitable for security-sensitive uses. crypto.randomUUID() in browsers and Node.js uses the system CSPRNG.

Should I switch all my existing v4 UUIDs to v7?

No — migrating existing IDs is usually not worth the effort. The benefits of v7 (sortability, index locality) apply to new inserts. If your system already has v4 UUIDs and index performance is acceptable, leave them. Start using v7 for new tables or new systems.

Can two machines generate the same UUID v4?

Theoretically yes, practically no. With 122 bits of randomness, you would need to generate about 2.7 × 10¹⁸ UUIDs before a 50% collision probability. At one billion UUIDs generated per second across all machines in the world, that would take over 85 years.

What is a nil UUID?

The nil UUID is all zeros: 00000000-0000-0000-0000-000000000000. It is used as a sentinel value (similar to null) to indicate “no UUID.” Some ORMs use it as a default value before an ID is assigned.

How do I validate a UUID format?

A simple regex check:

const UUID_REGEX =
  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

UUID_REGEX.test('550e8400-e29b-41d4-a716-446655440000'); // true
UUID_REGEX.test('not-a-uuid'); // false

For version-specific validation, check that position 14 (the version digit) matches the expected version number.