Understanding ULID: Features, Specification, and Python Usage Compared to UUID
This article explains what ULID is, why it can be a better alternative to UUID, outlines its key characteristics and specification, and provides practical Python examples for generating and handling ULIDs in distributed backend systems.
ULID (Universally Unique Lexicographically Sortable Identifier) is introduced as a globally unique identifier that combines a millisecond‑precision timestamp with a high‑entropy random component, offering collision‑free generation and lexicographic ordering.
Why not choose UUID – UUID has five versions, each with drawbacks such as reliance on MAC addresses, potential fragmentation, or pure randomness without ordering. UUID4 is common but still carries a risk of collisions.
Unlike UUID, ULID encodes both time and randomness, providing 1.21e+24 possible random values per millisecond, eliminating collision risk and producing a more readable string.
ULID Features
128‑bit compatibility with UUID
1.21e+24 unique IDs per millisecond
Lexicographic (alphabetical) sorting
Encoded in 26 characters using Crockford's Base32
Case‑insensitive and URL‑safe (no special characters)
Monotonic ordering within the same millisecond
ULID Specification
The current ULID specification (as implemented in python(ulid-py) ) defines a binary layout of 48‑bit timestamp and 80‑bit randomness.
01AN4Z07BY 79KA1307SR9X4MV3
|----------| |----------------|
Timestamp Randomness
10chars 16chars
48bits 80bitsComponents
Timestamp
48‑bit integer representing UNIX time in milliseconds
Valid until year 10889, ensuring ample space
Randomness
80‑bit random number, preferably generated with cryptographic quality
Sorting
The leftmost characters must appear first and the rightmost last, using the default ASCII set; ordering cannot be guaranteed for IDs generated within the same millisecond.
Encoding
ULID uses Crockford's Base32 alphabet (0123456789ABCDEFGHJKMNPQRSTVWXYZ) which omits I, L, O, and U to avoid confusion.
Binary Layout and Byte Order
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32_bit_uint_time_high |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 16_bit_uint_time_low | 16_bit_uint_random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32_bit_uint_random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32_bit_uint_random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+Application Scenarios
Replace auto‑increment primary keys in databases, eliminating DB involvement in key generation
Use in distributed systems as a UUID alternative, providing global uniqueness with millisecond ordering
Leverage the embedded timestamp for time‑based sharding or partitioning
Sort records by ULID when millisecond precision is sufficient, avoiding a separate timestamp column
Usage (Python)
Install the library:
pip install ulid-pyCreate a new ULID:
>> import ulid
>>> ulid.new()Generate a ULID from an existing UUID:
>> import ulid, uuid
>>> value = uuid.uuid4()
>>> value
UUID('0983d0a2-ff15-4d83-8f37-7dd945b5aa39')
>>> ulid.from_uuid(value)Create a ULID from a specific timestamp:
>> import datetime, ulid
>>> ulid.from_timestamp(datetime.datetime(1999, 1, 1))Generate a ULID from custom randomness:
>> import os, ulid
>>> randomness = os.urandom(10)
>>> ulid.from_randomness(randomness)Interact with a ULID object:
>> import ulid
>>> u = ulid.new()
>>> u
>>> u.timestamp()
>>> u.randomness()Open‑source repository: https://github.com/ahawker/ulid
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.