Skip to content
LowLevelDesign Mastery

UUID & ID Generation

Generating unique identifiers without coordination

In distributed systems, we need to generate unique identifiers for:

  • Database records: Primary keys
  • Request IDs: Tracking requests across services
  • File names: Avoiding collisions
  • Session IDs: User sessions
  • Order IDs: E-commerce orders

The Challenge: How do we generate unique IDs across multiple nodes without coordination? How do we ensure no collisions? How do we make IDs sortable and efficient?

Distributed ID generation problem: three nodes need unique IDs without coordination or collisions, solved by UUID, Snowflake or ULID

Sequential IDs (like database auto-increment) require coordination:

Sequential auto-increment IDs from a single database: single point of failure, bottleneck, poor scaling and information leakage

Problems:

  • Single point of failure: Database must be available
  • Bottleneck: All nodes compete for ID generation
  • Doesn’t scale: Can’t generate IDs in parallel
  • Reveals information: Sequential IDs reveal order count, user count

Not suitable for distributed systems!

UUID is a 128-bit identifier that’s globally unique without coordination.

UUID v4 uses 122 random bits (4 bits for version/variant).

Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx

  • 4 = version 4
  • y = variant (8, 9, A, or B)

Characteristics:

  • Globally unique: Collision probability extremely low
  • No coordination: Each node generates independently
  • Not sortable: Random, no time ordering
  • 128 bits: 16 bytes

Use Cases:

  • Distributed systems requiring uniqueness
  • When sortability not needed
  • When you don’t want to reveal information

UUID v7 uses timestamp + random bits (time-ordered).

Format: xxxxxxxx-xxxx-7xxx-yxxx-xxxxxxxxxxxx

  • 7 = version 7
  • First 48 bits = Unix timestamp (milliseconds)
  • Remaining bits = random

Characteristics:

  • Time-ordered: Sortable by generation time
  • Globally unique: Still very low collision probability
  • Better for indexing: Time-ordered improves database index performance
  • 128 bits: 16 bytes

Use Cases:

  • When sortability matters (database primary keys)
  • When you want time ordering
  • Better than v4 for database indexing

Snowflake is Twitter’s ID generation algorithm. Generates 64-bit time-ordered IDs.

64-bit ID:
├─ 41 bits: Timestamp (milliseconds since epoch)
├─ 10 bits: Machine ID (0-1023 machines)
└─ 12 bits: Sequence number (0-4095 per millisecond)

How It Works:

  1. Timestamp: Milliseconds since custom epoch (e.g., 2020-01-01)
  2. Machine ID: Unique ID for each machine (assigned at startup)
  3. Sequence: Increments for IDs generated in same millisecond

Performance:

  • ~4 million IDs per second per machine
  • Time-ordered (sortable)
  • Compact (64 bits = 8 bytes)

Requirements:

  • Clock synchronization: Machines must have synchronized clocks
  • Machine ID assignment: Need to assign unique machine IDs
  • Sequence management: Handle sequence overflow
Snowflake ID layout: 41-bit millisecond timestamp, 10-bit machine ID and 12-bit sequence in a compact, time-ordered 64-bit ID

ULID (Universally Unique Lexicographically Sortable Identifier)

Section titled “ULID (Universally Unique Lexicographically Sortable Identifier)”

ULID combines timestamp and randomness in a lexicographically sortable format.

128-bit ULID:
├─ 48 bits: Timestamp (milliseconds since 1970-01-01)
└─ 80 bits: Random

Format: 01ARZ3NDEKTSV4RRFFQ69G5FAV (Base32 encoded, 26 characters)

Characteristics:

  • Lexicographically sortable: String comparison matches time order
  • Time-ordered: First 48 bits are timestamp
  • URL-safe: Base32 encoding (no special characters)
  • 128 bits: Same size as UUID
  • Better than UUID v4: Time-ordered improves database performance

Use Cases:

  • Database primary keys (better indexing than UUID v4)
  • When you need sortable string IDs
  • When UUID v4 randomness causes index fragmentation
💡 Tip: Click dropdown to switch between languages
"id_generation.py
import uuid
import time
import random
from datetime import datetime, timezone
# UUID v4 (Random)
def generate_uuid_v4() -> str:
"""Generate UUID v4 (random)"""
return str(uuid.uuid4())
# UUID v7 (Time-ordered) - Python doesn't have built-in v7, so we'll simulate
def generate_uuid_v7() -> str:
"""Generate UUID v7-like (time-ordered)"""
# Get current timestamp in milliseconds
timestamp_ms = int(time.time() * 1000)
# Extract timestamp bits (48 bits = 6 bytes)
timestamp_bytes = timestamp_ms.to_bytes(6, byteorder='big')
# Generate random bytes for remaining 10 bytes
random_bytes = random.randbytes(10)
# Combine: timestamp (6 bytes) + random (10 bytes) = 16 bytes
combined = timestamp_bytes + random_bytes
# Create UUID-like format (set version 7, variant bits)
uuid_bytes = bytearray(combined)
uuid_bytes[6] = (uuid_bytes[6] & 0x0F) | 0x70 # Version 7
uuid_bytes[8] = (uuid_bytes[8] & 0x3F) | 0x80 # Variant
return str(uuid.UUID(bytes=bytes(uuid_bytes)))
# Snowflake Algorithm
class SnowflakeGenerator:
"""Snowflake ID generator"""
# Custom epoch (2020-01-01 00:00:00 UTC)
EPOCH = 1577836800000 # milliseconds
def __init__(self, machine_id: int):
if machine_id < 0 or machine_id > 1023:
raise ValueError("Machine ID must be between 0 and 1023")
self.machine_id = machine_id
self.sequence = 0
self.last_timestamp = 0
def generate(self) -> int:
"""Generate Snowflake ID"""
timestamp = int(time.time() * 1000) - self.EPOCH
if timestamp < self.last_timestamp:
raise Exception("Clock moved backwards")
if timestamp == self.last_timestamp:
# Same millisecond, increment sequence
self.sequence = (self.sequence + 1) & 0xFFF # 12 bits
if self.sequence == 0:
# Sequence overflow, wait for next millisecond
timestamp = self._wait_next_millisecond(self.last_timestamp)
else:
self.sequence = 0
self.last_timestamp = timestamp
# Build ID: timestamp (41 bits) | machine_id (10 bits) | sequence (12 bits)
snowflake_id = (timestamp << 22) | (self.machine_id << 12) | self.sequence
return snowflake_id
def _wait_next_millisecond(self, last_timestamp: int) -> int:
"""Wait until next millisecond"""
timestamp = int(time.time() * 1000) - self.EPOCH
while timestamp <= last_timestamp:
timestamp = int(time.time() * 1000) - self.EPOCH
return timestamp
# ULID Generation
import base64
def generate_ulid() -> str:
"""Generate ULID (time-ordered, lexicographically sortable)"""
# Get current timestamp in milliseconds
timestamp_ms = int(time.time() * 1000)
# Timestamp: 48 bits (6 bytes)
timestamp_bytes = timestamp_ms.to_bytes(6, byteorder='big')
# Random: 80 bits (10 bytes)
random_bytes = random.randbytes(10)
# Combine: timestamp + random = 16 bytes
ulid_bytes = timestamp_bytes + random_bytes
# Base32 encode (URL-safe, no padding)
# Using crockford's base32 alphabet
alphabet = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'
ulid = ''
value = int.from_bytes(ulid_bytes, byteorder='big')
while value > 0:
ulid = alphabet[value % 32] + ulid
value //= 32
# Pad to 26 characters
ulid = ulid.zfill(26)
return ulid
# Usage
print("UUID v4:", generate_uuid_v4())
print("UUID v7:", generate_uuid_v7())
snowflake = SnowflakeGenerator(machine_id=1)
print("Snowflake:", snowflake.generate())
print("Snowflake:", snowflake.generate())
print("ULID:", generate_ulid())
FeatureUUID v4UUID v7SnowflakeULID
Size128 bits128 bits64 bits128 bits
SortableNoYesYesYes
CoordinationNoneNoneMachine ID neededNone
PerformanceFastFastVery fastFast
Collision RiskVery lowVery lowVery lowVery low
Database IndexingPoor (random)Good (time-ordered)ExcellentExcellent

Use UUID v4 when:

  • Need globally unique IDs
  • Sortability not important
  • Don’t want to reveal information
  • Simple implementation needed

Use UUID v7 when:

  • Need time-ordered IDs
  • Better database indexing
  • Want UUID format compatibility
  • Can use newer UUID version

Use Snowflake when:

  • Need compact IDs (64 bits)
  • Very high throughput needed
  • Can coordinate machine IDs
  • Time-ordered important

Use ULID when:

  • Need lexicographically sortable string IDs
  • Better database indexing than UUID v4
  • URL-safe format needed
  • Time-ordered important

No Coordination

UUID, ULID generate unique IDs without central coordination. Snowflake needs machine ID assignment but no runtime coordination.

Time-Ordered

UUID v7, Snowflake, ULID are time-ordered. Improves database indexing performance compared to random UUID v4.

Collision Probability

All algorithms have extremely low collision probability. UUID v4: ~5×10^-37 for single collision. Practical systems don’t worry about collisions.

Choose Wisely

Consider size, sortability, coordination needs, and database indexing when choosing ID generation strategy.