UUIDs are everywhere — in databases, logs, APIs, and analytics platforms. But not all UUIDs are created equal, especially when it comes to speed vs. security.
In this article, we benchmark several UUID generation methods in Python, show you their trade-offs, and help you pick the best one for your needs.
🧬 The Contenders
Python’s built-in uuid
module provides several ways to generate UUIDs:
uuid1()
— time and MAC address-baseduuid4()
— fully random (cryptographically secure)uuid5()
— SHA-1 + namespace-based (deterministic)
We'll also include:
secrets.token_hex(16)
— fast secure random string (not a UUID)os.urandom(16)
— raw CSPRNG output
🧪 Benchmark Setup
We used timeit
with 1 million iterations per function on Python 3.11.
import timeit
import uuid
import secrets
import os
iterations = 1_000_000
results = {
"uuid1": timeit.timeit("uuid.uuid1()", setup="import uuid", number=iterations),
"uuid4": timeit.timeit("uuid.uuid4()", setup="import uuid", number=iterations),
"uuid5": timeit.timeit("uuid.uuid5(uuid.NAMESPACE_DNS, 'example.com')", setup="import uuid", number=iterations),
"secrets.token_hex": timeit.timeit("secrets.token_hex(16)", setup="import secrets", number=iterations),
"os.urandom": timeit.timeit("os.urandom(16)", setup="import os", number=iterations),
}
⚡ Performance Results
Method | Total Time (1M runs) | Per Call (µs) |
---|---|---|
uuid.uuid1() | ~1.0 sec | ~1.0 µs |
uuid.uuid4() | ~1.3 sec | ~1.3 µs |
uuid.uuid5() | ~5.8 sec | ~5.8 µs |
secrets.token_hex() | ~1.0 sec | ~1.0 µs |
os.urandom(16) | ~0.9 sec | ~0.9 µs |
> 💡 These numbers will vary slightly by hardware, OS, and Python version — but the trend is consistent.
🛡️ Security + Randomness Considerations
`uuid.uuid1()`
- Not cryptographically safe
- Contains MAC address + timestamp
- Useful for internal tracing, but never expose it in public APIs
`uuid.uuid4()`
- Cryptographically secure (uses
os.urandom
) - Standard choice for unique, unguessable IDs
- Slightly slower than
uuid1()
, but safer
`uuid.uuid5()`
- Deterministic — same input → same UUID
- Good for namespacing, e.g. DNS, resource IDs
- Slowest of the bunch due to hashing
`secrets.token_hex(16)`
- Not a UUID, but cryptographically strong
- Great for compact tokens
- Faster than
uuid4()
and just as secure
`os.urandom(16)`
- Raw secure entropy
- Use if you want a custom 16-byte ID in binary or hex
✍️ Code Examples
UUIDv4 (Recommended Default)
import uuid
user_id = uuid.uuid4()
UUIDv5 (Deterministic)
import uuid
resource_id = uuid.uuid5(uuid.NAMESPACE_URL, "https://example.com/resource")
Secure Token (Not a UUID, but fast + safe)
import secrets
api_token = secrets.token_hex(16) # 32-char hex string
⚙️ When to Use What
Use Case | Best Option |
---|---|
Public API ID | uuid.uuid4() |
Trace ID in logs | uuid.uuid1() |
Deterministic ID (by name) | uuid.uuid5() |
Fast, secure token | secrets.token_hex() |
Custom binary ID | os.urandom(16) |
🧠 Pro Tips
- Always use
uuid4()
for general-purpose ID generation - Avoid
uuid1()
in untrusted contexts — it leaks timestamp + MAC - Store UUIDs as
BINARY(16)
in databases for speed/space efficiency - If you don’t need UUID format,
secrets.token_hex(16)
is faster and shorter - Never use
random.random()
for IDs — it’s not secure
🧪 Bonus: Parallel Generation
Want to speed up ID creation? Use multiprocessing:
from multiprocessing import Pool
import uuid
def gen():
return uuid.uuid4()
with Pool(4) as p:
results = p.map(lambda _: gen(), range(1_000_000))
Python’s uuid4()
is thread-safe and uses system-level CSPRNG, so it scales well in multi-core environments.
Final Thoughts
UUIDs are deceptively simple — but choosing the right generation method can have big implications for performance, security, and uniqueness at scale.
Unless you have special needs (e.g., determinism, MAC tracing), `uuid4()` is the default winner.
Use it well, log it liberally, and never — ever — fake it with random()
.