Fundamentals 7 min read

Understanding ULID: Features, Specification, and Python Usage Compared to UUID

This article explains ULID—a lexicographically sortable, 128‑bit identifier combining timestamp and randomness—detailing its advantages over UUID, specifications, binary layout, common use cases, and provides Python code examples for generating and manipulating ULIDs.

Architecture Digest
Architecture Digest
Architecture Digest
Understanding ULID: Features, Specification, and Python Usage Compared to UUID

ULID (Universally Unique Lexicographically Sortable Identifier) is a 128‑bit identifier that combines a millisecond‑precision timestamp with 80 bits of randomness, offering lexicographic ordering and URL‑safe encoding.

UUID has five versions; version 4 (random) is most common but can still collide. Unlike UUID, ULID embeds time, providing 1.21×10²⁴ unique values per millisecond and eliminating collision risk while remaining 128‑bit compatible.

Key ULID characteristics include:

Compatibility with the 128‑bit size of UUID.

1.21×10²⁴ unique IDs per millisecond.

Lexicographic (dictionary) ordering.

Encoded as a 26‑character Crockford Base32 string (no ambiguous characters, case‑insensitive, URL‑safe).

Monotonic ordering within the same millisecond when using the default algorithm.

Specification

Timestamp : 48‑bit integer representing UNIX time in milliseconds (valid until year 10889).

Randomness : 80‑bit random value, preferably generated with cryptographic quality.

Sorting : Characters are ordered lexicographically; the leftmost character is the most significant.

Encoding : Crockford Base32 alphabet (0123456789ABCDEFGHJKMNPQRSTVWXYZ) without I, L, O, U.

Binary layout (network 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 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Typical application scenarios include replacing auto‑increment primary keys, generating globally unique keys without database coordination, sharding databases by embedded timestamp, and ordering records when millisecond precision is sufficient.

Python usage (ulid‑py)

pip install ulid-py

Creating a new ULID:

>>> import ulid
>>> ulid.new()

Creating a ULID from an existing UUID:

>>> import ulid, uuid
>>> value = uuid.uuid4()
>>> value
UUID('0983d0a2-ff15-4d83-8f37-7dd945b5aa39')
>>> ulid.from_uuid(value)

Creating a ULID from a specific timestamp:

>>> import datetime, ulid
>>> ulid.from_timestamp(datetime.datetime(1999, 1, 1))

Creating a ULID from raw randomness:

>>> import os, ulid
>>> randomness = os.urandom(10)
>>> ulid.from_randomness(randomness)

Once you have a ULID object, you can retrieve its components:

>>> u = ulid.new()
>>> u.timestamp()
>>> u.randomness()

For more information, see the ULID project on GitHub: https://github.com/ahawker/ulid .

distributed systemsPythontimestampUUIDunique identifierULID
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.