Python uuid: Unique and Sortable IDs

Published on: July 30, 2026
Reading time: 7 minutes
Digital code representing unique and sortable UUID identifiers in Python

Identifiers appear in users, orders, files, events, messages, audit records, and distributed systems. A numeric sequence works inside one database, but it requires coordination when several services create data at the same time. The Python uuid module provides immutable 128-bit objects that can be generated without a central authority and transported as text, bytes, or integers.

This guide explains when to use UUIDv4, UUIDv5, UUIDv6, and UUIDv7, why UUIDv1 can expose information, how to validate strings, store values in databases, sort identifiers, and avoid confusing uniqueness with security. It complements our guides about Python lists, collections, copying objects, time zones, and temporary files.

What a UUID is

UUID stands for Universally Unique Identifier. The format contains 128 bits and is commonly displayed as 32 hexadecimal digits separated by hyphens:

550e8400-e29b-41d4-a716-446655440000

The goal is decentralized generation with an extremely low collision probability. It is not an absolute mathematical guarantee for every version and implementation, but it is sufficient for most systems.

Generating UUIDv4

For a general random identifier, uuid4() is the most common choice.

import uuid

identifier = uuid.uuid4()
print(identifier)
print(type(identifier))
print(identifier.version)

The returned value is a UUID object, not merely a string. Python generates UUIDv4 values with cryptographically secure randomness, reducing collisions and avoiding direct exposure of time or network-address data.

String, bytes, integer, and URN forms

A UUID instance exposes several useful representations.

import uuid

value = uuid.uuid4()

print(str(value))
print(value.hex)
print(value.bytes)
print(value.int)
print(value.urn)

str() produces the standard hyphenated format. hex returns 32 characters without hyphens. bytes uses 16 bytes. int exposes the complete 128-bit integer. urn produces a value such as urn:uuid:....

Validating input with UUID()

When an API or command line receives an identifier, convert it before using it.

from uuid import UUID


def parse_uuid(text: str) -> UUID:
    try:
        return UUID(text)
    except (ValueError, AttributeError, TypeError) as error:
        raise ValueError("Invalid UUID") from error

The constructor accepts hyphenated strings, compact hexadecimal text, braces, and URN prefixes. After conversion, comparisons and version checks are consistent.

UUIDv1 and privacy

uuid1() combines time, a clock sequence, and a node identifier. Historically, the node can be derived from the machine’s MAC address.

import uuid

value = uuid.uuid1()
print(value)
print(value.node)
print(value.time)

The official uuid documentation warns that UUIDv1 may compromise privacy by including the network address. Avoid it in new systems unless compatibility requires its semantics.

UUIDv3 and UUIDv5: deterministic IDs

UUIDv3 and UUIDv5 return the same identifier for the same namespace and name. Version 3 uses MD5; version 5 uses SHA-1 and is generally preferred between the two.

import uuid

url_id = uuid.uuid5(
    uuid.NAMESPACE_URL,
    "https://example.com/products/42",
)

print(url_id)

This behavior is useful for migrations, idempotent imports, URL-derived keys, and synchronization between systems. Names must be canonicalized: case, trailing slashes, Unicode normalization, and encoding differences produce different UUIDs.

Predefined namespaces

The module includes four well-known namespaces:

  • NAMESPACE_DNS for domain names;
  • NAMESPACE_URL for URLs;
  • NAMESPACE_OID for ISO OIDs;
  • NAMESPACE_X500 for X.500 names.

An organization can also generate and preserve its own namespace UUID. Treat it as part of the data contract because changing it changes every derived identifier.

UUIDv6: reordered time fields

UUIDv6 rearranges UUIDv1 time fields to improve database index locality. It was added to Python in version 3.14.

import uuid

value = uuid.uuid6()
print(value)
print(value.version)

It is most useful for systems that already depend on UUIDv1 semantics and need a more database-friendly byte order. New applications without v1 compatibility should usually evaluate UUIDv7.

UUIDv7: Unix time and sorting

UUIDv7 embeds a Unix timestamp in milliseconds in the most significant bits and combines the remaining space with randomness and monotonicity mechanisms.

import datetime as dt
import uuid

value = uuid.uuid7()
created_at = dt.datetime.fromtimestamp(
    value.time / 1000,
    tz=dt.timezone.utc,
)

print(value)
print(created_at)

Values generated in sequence tend to sort chronologically, improving B-tree locality compared with UUIDv4. This does not turn the identifier into a reliable audit timestamp; keep an explicit datetime column.

Monotonicity within one millisecond

An application can generate many UUIDv7 values during the same millisecond. Python’s implementation uses a counter to preserve monotonicity where the platform lacks finer precision.

Useful ordering is not a gapless sequence. Different processes, restarts, and clock adjustments may require additional business rules. Use explicit sequence numbers when the domain needs strict total order.

UUIDv8: custom formats

UUIDv8 reserves fields for experimental or vendor-specific layouts.

import uuid

value = uuid.uuid8(
    0x12345678,
    0x9ABC,
    0x11223344,
)
print(value)

Arguments have bit limits, and excess bits are truncated. By default, components are not generated with a cryptographically secure random generator. UUIDv8 is therefore not a replacement for UUIDv4 in security-sensitive contexts.

NIL and MAX UUIDs

Python 3.14 also provides special values:

import uuid

print(uuid.NIL)
print(uuid.MAX)

NIL has every bit set to zero and can represent “no UUID” in protocols that require 128 bits. MAX has every bit set to one and can serve as an upper sentinel. In databases, prefer a real NULL when absence belongs to the model.

Comparison and sorting

UUID objects compare through their integer value.

values = [uuid.uuid7() for _ in range(5)]
ordered = sorted(values)
assert ordered == values

Comparing a UUID with an unrelated type raises TypeError. Normalize incoming values before sorting or using them as dictionary keys.

Database storage options

Common representations include:

  • a native UUID database type;
  • 16 binary bytes;
  • 36-character hyphenated text;
  • 32-character hexadecimal text.

The native type usually provides validation and useful operators. Binary storage saves space but requires careful byte-order handling and may be harder to inspect. Text is convenient but larger.

UUIDs as primary keys

UUIDs allow clients and services to create records before contacting a central database. They also avoid directly revealing record counts, unlike sequential integers.

UUIDv4 causes random index insertion and can increase fragmentation. UUIDv7 improves locality but remains larger than a 64-bit integer. Benchmark the trade-off with your database, row count, and query pattern.

A UUID is not authorization

A hard-to-guess identifier does not replace access control. Even a UUIDv4 is only the name of a resource.

# wrong: grant access because the UUID exists
# correct: verify user, tenant, role, and ownership

APIs must enforce authentication and authorization for every operation. Do not use a UUID as a password, session token, or cryptographic secret.

Collisions and unique constraints

The collision probability of UUIDv4 is extremely low, but the database should still enforce a primary key or UNIQUE constraint. The generator reduces probability; the constraint protects integrity.

If a conflict occurs, generate another value and retry in a controlled transaction. Never remove uniqueness enforcement because “UUIDs cannot collide.”

Deterministic IDs and idempotency

UUIDv5 works well when data has a stable natural key.

def customer_id(system: str, code: str) -> uuid.UUID:
    name = f"{system.strip().lower()}:{code.strip()}"
    return uuid.uuid5(uuid.NAMESPACE_URL, name)

Canonicalization must be documented and tested. When a future version changes the rule, version the namespace or include a rule version in the name.

JSON serialization

The standard json module does not serialize UUID objects automatically. Convert them to strings at the boundary.

import json
import uuid

record = {"id": str(uuid.uuid4()), "name": "Ana"}
text = json.dumps(record)
print(text)

Parse the string back with UUID(). Web frameworks and ORM serializers often support UUID fields directly.

Command-line usage

Since Python 3.12, the module can run as a script. Python 3.14 added versions 6, 7, and 8 plus generation of multiple values.

python -m uuid
python -m uuid -u uuid7
python -m uuid -C 10
python -m uuid -u uuid5 -n @url -N https://example.com

This is useful in tests, migrations, and deployment scripts without writing a separate program.

RFC 9562 and compatibility

RFC 9562, published in 2024, replaced RFC 4122. It retains traditional versions and standardizes UUIDv6, UUIDv7, UUIDv8, NIL, and MAX.

Older code may still display the constant RFC_4122 for compatibility. The name remains, while the current layout is described by the newer RFC.

Common mistakes

  • Using UUIDv1 without considering privacy.
  • Choosing UUIDv4 and expecting time ordering.
  • Using UUIDv7 as the only audit timestamp.
  • Treating a UUID as an access token.
  • Storing unvalidated text.
  • Removing database uniqueness constraints.
  • Using UUIDv5 without canonicalizing the name.
  • Adopting UUIDv8 without documenting the format.

Best practices

  • Use UUIDv4 for general random identifiers.
  • Use UUIDv5 for deterministic name-based IDs.
  • Consider UUIDv7 for databases that benefit from time ordering.
  • Avoid UUIDv1 in new systems because of privacy concerns.
  • Use native UUID database types when possible.
  • Keep uniqueness constraints.
  • Separate identity from authorization.
  • Test parsing, versions, round trips, and ordering.

Conclusion

The Python uuid module provides different identifier families for different contracts. UUIDv4 gives secure randomness, UUIDv5 produces deterministic values, UUIDv6 improves the time layout of legacy UUIDv1, and UUIDv7 combines Unix time with database-friendly locality. UUIDv8 remains a space for custom formats.

The right choice depends on generation, ordering, reproducibility, and privacy requirements. With validation, an appropriate database type, unique constraints, and independent authorization, UUIDs let distributed systems create identifiers without central coordination or global sequences.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Organized files representing secure temporary storage in Python
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python tempfile: Secure Temporary Files

    Learn Python tempfile to create secure temporary files and directories with automatic cleanup across Windows and Unix.

    Ler mais

    Tempo de leitura: 7 minutos
    29/07/2026
    Clocks representing international time zones with Python zoneinfo
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python zoneinfo: Time Zones Done Right

    Learn Python zoneinfo to convert time zones, handle daylight saving, fold, UTC, and tzdata without scheduling mistakes.

    Ler mais

    Tempo de leitura: 6 minutos
    29/07/2026
    Duplicate document icon representing shallow and deep copies in Python
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python copy: Shallow and Deep Copies

    Learn Python copy to create shallow and deep copies, replace fields, and avoid sharing nested mutable objects by mistake.

    Ler mais

    Tempo de leitura: 6 minutos
    28/07/2026
    Monitor showing binary search and sorted Python lists
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python bisect: Keep Lists Sorted

    Learn Python bisect to keep lists sorted, locate ranges, find neighbors, and insert values efficiently with binary search.

    Ler mais

    Tempo de leitura: 7 minutos
    27/07/2026
    Developer implementing a priority queue with Python heapq
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python heapq: Build Priority Queues

    Learn Python heapq to build priority queues, find the smallest values, and process tasks efficiently with binary heaps.

    Ler mais

    Tempo de leitura: 6 minutos
    26/07/2026
    Logo do Python com expressão pensativa sobreposto a uma biblioteca com estantes cheias de livros, representando o conceito de bibliotecas em Python
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python Libraries: What They Are and How to Use Them

    Learn what Python libraries are, how modules and packages differ, how to install and import them, use virtual environments, and

    Ler mais

    Tempo de leitura: 6 minutos
    10/07/2026