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.

How to generate a unique ID in Python
Published on: 
Wed
Mar 25, 2026
Updated on: 
Thu
Mar 26, 2026
The Replit Team

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 and ORDER_ 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().bytes provides 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 increment self._counter at 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 UUID comparison correctly: A common mistake is comparing a UUID object directly with its string version. They won't evaluate as equal because they are different data types. Always convert the string back into a UUID object before checking for equality.
  • Making UUID objects JSON serializable: You'll get a TypeError if you try to serialize a UUID object into JSON, as the library doesn't handle this type natively. The fix is to convert the UUID to a string with str() before serialization.
  • Choosing the right uuid version: Use uuid4() for random, unpredictable identifiers suitable for most cases. Opt for uuid5() 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 a YYYYMMDD string.
  • 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 from uuid.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.

Get started free

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.

Get started for free

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.