How to generate a unique ID in Python
Learn how to generate unique IDs in Python. Explore different methods, tips, real-world applications, and common error debugging.

Unique IDs are essential to track data, sessions, and transactions. Python offers robust built-in modules to create them. This ensures data integrity and helps prevent conflicts within your applications.
In this article, you'll explore several techniques to generate unique IDs. You'll find practical tips, real-world applications, and debugging advice to help you select the right approach for your project.
Basic UUID generation with the uuid module
import uuid
unique_id = uuid.uuid4()
print(f"Generated UUID: {unique_id}")--OUTPUT--Generated UUID: 550e8400-e29b-41d4-a716-446655440000
Python's built-in uuid module is the go-to for creating Universally Unique Identifiers. The code uses uuid.uuid4() to generate a version 4 UUID, which is created from random numbers. This approach is popular for a few key reasons:
- High Uniqueness: The chance of two
uuid4()values colliding is astronomically low, making them reliable for distributed systems without a central authority. - Security: Since they aren't based on a machine's MAC address or the time, they don't leak potentially sensitive information about the host system.
Standard approaches for generating unique identifiers
While the uuid module offers a robust solution, sometimes your project calls for a different approach, like using timestamps, prefixed random values, or simple sequential counters.
Using timestamps for time-based identifiers
import time
timestamp_id = int(time.time() * 1000)
print(f"Timestamp ID: {timestamp_id}")--OUTPUT--Timestamp ID: 1634567890123
This method creates a unique ID from the current time. The time.time() function returns the number of seconds since the Unix epoch. Multiplying by 1000 converts this to milliseconds for greater precision, and int() creates a clean integer ID.
- Chronological Sorting: Timestamp-based IDs are inherently sortable, which is useful for ordering events or records by their creation time.
- Collision Risk: In high-traffic systems, multiple requests within the same millisecond could generate identical IDs, making this approach less reliable than UUIDs for guaranteeing uniqueness.
Combining random values with prefixes
import random
import string
prefix = "USER"
random_part = ''.join(random.choices(string.ascii_uppercase + string.digits, k=8))
prefixed_id = f"{prefix}_{random_part}"
print(f"Prefixed ID: {prefixed_id}")--OUTPUT--Prefixed ID: USER_A7B2C9D3
Adding a prefix to a random string creates human-readable identifiers. This approach combines a static prefix, like "USER", with a randomly generated sequence. The code uses random.choices() to build an 8-character string from uppercase letters and digits, ensuring a good degree of randomness.
- Improved Readability: Prefixes make it easy to distinguish between different types of IDs, such as
USER_for users andORDER_for orders. - Easier Debugging: When you see a prefixed ID in logs or a database, you instantly know its context without needing to look it up.
Creating sequential identifiers with counters
class IDGenerator:
def __init__(self, prefix="ID", start=1):
self.prefix = prefix
self.counter = start
def next_id(self):
id_value = f"{self.prefix}{self.counter:04d}"
self.counter += 1
return id_value
generator = IDGenerator("ITEM")
print(generator.next_id(), generator.next_id(), generator.next_id())--OUTPUT--ITEM0001 ITEM0002 ITEM0003
This method creates predictable, ordered IDs using a simple counter. The IDGenerator class manages the state, so each call to next_id() produces a unique value in the sequence. It combines a prefix with an incrementing number, formatted with leading zeros for consistency.
- Simplicity and Order: The main benefit is its straightforwardness. The resulting IDs are sequential, which is great for simple, single-instance applications.
- Scalability Limits: This approach doesn't work well in distributed systems. Different generator instances would create conflicting IDs, so it's best for scenarios where a single source manages ID creation.
Advanced techniques for specialized unique IDs
When the standard approaches don't quite fit, you can leverage more advanced techniques to handle specialized requirements like determinism, brevity, or concurrency.
Named UUIDs with uuid.uuid5()
import uuid
namespace = uuid.NAMESPACE_URL
name = "https://example.com/user/john"
deterministic_id = uuid.uuid5(namespace, name)
print(f"Deterministic UUID: {deterministic_id}")--OUTPUT--Deterministic UUID: c49a3b66-3c42-5b61-9100-847a61e0fb39
Unlike the random IDs from uuid.uuid4(), the uuid.uuid5() function generates a deterministic UUID. This means you'll get the exact same ID every time you provide the same namespace and name. It’s perfect for situations where you need to recreate an identifier consistently across different systems or sessions.
- Idempotency: You can ensure an operation on a specific piece of data happens only once, since the ID will always be the same for that data.
- Consistency: It's great for deriving a unique ID from a URL or user email without needing to store a mapping.
Creating shorter unique IDs with base conversion
import uuid
import base64
def short_uuid():
raw_uuid = uuid.uuid4().bytes
encoded = base64.urlsafe_b64encode(raw_uuid).decode('ascii').rstrip('=')
return encoded
print(f"Short ID: {short_uuid()}")--OUTPUT--Short ID: YfDs7Y1dSjCu9fx7t1woLA
A full 36-character UUID can be unwieldy, especially in URLs. This technique creates a shorter, URL-friendly ID by converting a standard UUID into a more compact Base64 representation. You get the same level of uniqueness in a much smaller package.
- First,
uuid.uuid4().bytesprovides the raw 16-byte value of the identifier. - Next,
base64.urlsafe_b64encode()transforms these bytes into a compact string using URL-safe characters. - Finally,
rstrip('=')cleans up the output by removing any unnecessary padding.
Thread-safe unique ID generation
import threading
import time
class ThreadSafeIDGenerator:
def __init__(self):
self._lock = threading.Lock()
self._counter = 0
def generate_id(self):
with self._lock:
self._counter += 1
return f"ID-{int(time.time())}-{self._counter}"
generator = ThreadSafeIDGenerator()
print(generator.generate_id())--OUTPUT--ID-1634567891-1
When multiple threads run at once, a simple counter can fail and produce duplicate IDs. This happens if two threads read and increment the counter at the same time—a classic race condition. This code solves the problem by making the ID generation process thread-safe.
- It uses a
threading.Lock()to protect the shared counter from simultaneous access. - The
with self._lock:statement ensures only one thread can incrementself._counterat any given moment. - This approach guarantees each ID is unique, even in a concurrent application.
Move faster with Replit
Replit is an AI-powered development platform that transforms natural language into working applications. Describe what you want to build, and Replit Agent creates it—complete with databases, APIs, and deployment.
For the unique ID techniques we've explored, Replit Agent can turn them into production-ready tools. You can use it to build complete applications based on the concepts in this article.
- Build an event logging service that uses timestamp-based IDs for chronological sorting and analysis.
- Create a user management dashboard where each user is assigned a human-readable, prefixed ID like
USER_A7B2C9D3. - Deploy a URL shortener that generates compact, collision-resistant identifiers for links using short UUIDs.
Describe your app idea, and Replit Agent writes the code, tests it, and fixes issues automatically, all in your browser. Try building your next application with Replit Agent.
Common errors and challenges
Generating unique IDs is usually straightforward, but a few common challenges can trip you up if you're not prepared for them.
- Handling
UUIDcomparison correctly: A common mistake is comparing aUUIDobject directly with its string version. They won't evaluate as equal because they are different data types. Always convert the string back into aUUIDobject before checking for equality. - Making
UUIDobjects JSON serializable: You'll get aTypeErrorif you try to serialize aUUIDobject into JSON, as the library doesn't handle this type natively. The fix is to convert theUUIDto a string withstr()before serialization. - Choosing the right
uuidversion: Useuuid4()for random, unpredictable identifiers suitable for most cases. Opt foruuid5()only when you need a deterministic ID that you can consistently regenerate from the same input, like a URL.
Handling UUID comparison correctly
It's a common pitfall to compare a UUID object directly to its string form. Even if they represent the same value, they're different data types, so a direct comparison with == will always fail. The code below shows this in action.
import uuid
id1 = uuid.uuid4()
id2 = str(id1)
# This will always be False
if id1 == id2:
print("IDs match")
else:
print("IDs don't match")
The comparison id1 == id2 fails because you're checking a UUID object against a string. Python sees them as different types, even if their underlying values are identical. The following example shows how to handle this correctly.
import uuid
id1 = uuid.uuid4()
id2 = str(id1)
# Compare UUID with string properly
if id1 == uuid.UUID(id2):
print("IDs match")
else:
print("IDs don't match")
To make the comparison work, you need to convert the string back into a `UUID` object. The uuid.UUID() constructor handles this perfectly. By calling uuid.UUID(id2), you're comparing two UUID objects, so the check will return True. You'll often encounter this situation when pulling UUIDs from a database or an API response, where they are typically stored as strings.
Making UUID objects JSON serializable
When you try to convert data containing a UUID object to a JSON string, you'll hit a TypeError. This is because the standard json library doesn't recognize the UUID type by default. The code below shows this common error in action.
import uuid
import json
data = {
"id": uuid.uuid4(),
"name": "John"
}
# This will raise TypeError: UUID is not JSON serializable
json_data = json.dumps(data)
print(json_data)
The code triggers a TypeError because the json.dumps() function expects primitive data types like strings, not complex Python objects like UUID. See how you can resolve this in the following example.
import uuid
import json
data = {
"id": str(uuid.uuid4()),
"name": "John"
}
json_data = json.dumps(data)
print(json_data)
The fix is to convert the UUID object into a string before serializing it. By calling str(uuid.uuid4()), you give the json.dumps() function a data type it can handle. You'll need to do this whenever you're preparing data for an API response or saving it to a JSON file, as the standard library can't process UUID objects on its own. This simple conversion ensures your data is always in a compatible format.
Choosing the right uuid version for your needs
Selecting the right UUID version is more than a minor detail—it's fundamental to your application's logic. Using a random uuid.uuid4() when you need a consistent, deterministic ID can lead to data integrity issues. The following example demonstrates this common pitfall.
import uuid
# Using random UUID when deterministic ID is needed
user_email = "[email protected]"
user_id = uuid.uuid4() # Different every time
print(f"User ID for {user_email}: {user_id}")
Each time this code runs, uuid.uuid4() creates a new, unpredictable ID for the same user_email. This prevents you from reliably retrieving the user's ID later. See how to generate a consistent ID from the email in the following example.
import uuid
# Using namespace UUID for deterministic ID
user_email = "[email protected]"
namespace = uuid.NAMESPACE_URL
user_id = uuid.uuid5(namespace, user_email) # Same every time
print(f"User ID for {user_email}: {user_id}")
The correct approach uses uuid.uuid5() to generate a deterministic ID from a namespace and a name. By using the user's email as the name, you'll get the same unique ID every time for that specific email. This is essential when you need to consistently derive an identifier from a piece of data, like a URL or email, without having to store and look up the relationship. Use uuid.uuid4() for randomness, but uuid.uuid5() for consistency.
Real-world applications
After navigating the common pitfalls, you can confidently apply these ID generation techniques to solve practical, real-world programming challenges.
Using uuid for timestamped file naming
By combining a timestamp with a short UUID segment, you can generate unique and sortable filenames that are perfect for organizing logs or versioned files.
import uuid
from datetime import datetime
def create_timestamped_filename(prefix, extension):
timestamp = datetime.now().strftime("%Y%m%d")
unique_id = str(uuid.uuid4())[:8]
return f"{prefix}_{timestamp}_{unique_id}.{extension}"
log_file = create_timestamped_filename("server_log", "txt")
config_file = create_timestamped_filename("config", "json")
print(f"Log file: {log_file}")
print(f"Config file: {config_file}")
The create_timestamped_filename function builds a structured, unique filename. It uses an f-string to assemble a custom prefix, a file extension, and two key dynamic components.
- A timestamp from
datetime.now()is formatted into aYYYYMMDDstring. - An 8-character segment from a
uuid.uuid4()call provides a random element.
This method guarantees distinct filenames, preventing accidental overwrites by adding a unique identifier to each name—even for files generated on the same day with the same prefix.
Building a user activity tracking system with uuid
You can build a robust user activity tracker by assigning a unique session ID from uuid.uuid4() each time a user starts a new session.
import uuid
from datetime import datetime
class UserActivityTracker:
def __init__(self):
self.sessions = {}
def start_session(self, user_id):
session_id = str(uuid.uuid4())[:12]
self.sessions[session_id] = {
'user_id': user_id,
'start_time': datetime.now(),
'actions': []
}
return session_id
def log_action(self, session_id, action):
if session_id in self.sessions:
self.sessions[session_id]['actions'].append({
'action': action,
'timestamp': datetime.now()
})
tracker = UserActivityTracker()
session = tracker.start_session("user123")
tracker.log_action(session, "login")
tracker.log_action(session, "view_dashboard")
print(f"Session ID: {session}")
print(f"Actions logged: {len(tracker.sessions[session]['actions'])}")
The UserActivityTracker class is designed to monitor user interactions within a session. It uses a dictionary to store all active sessions, with each one identified by a unique key.
- The
start_session()method kicks things off by generating a 12-character ID fromuuid.uuid4(). It creates a new session record containing the user ID and start time. - The
log_action()method appends timestamped events to a specific session's action list, allowing you to track a user's journey.
Get started with Replit
Now, turn these techniques into a real application. Tell Replit Agent to build “a URL shortener with compact, unique IDs” or “a logging tool that creates timestamped filenames with a UUID suffix.”
The agent writes the code, tests for errors, and deploys your app automatically. Start building with Replit.
Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.
Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.

.png)
.png)
.png)