Fundamentals 8 min read

Understanding ULID: A Better Alternative to UUID

ULID (Universally Unique Lexicographically Sortable Identifier) combines a millisecond-precision timestamp with high-entropy randomness to produce 128‑bit, URL‑safe, lexicographically sortable IDs that avoid UUID’s collision risks, offering advantages such as monotonic ordering, compact Base32 encoding, and suitability for distributed database primary keys.

Architect's Tech Stack
Architect's Tech Stack
Architect's Tech Stack
Understanding ULID: A Better Alternative to UUID

ULID (Universally Unique Lexicographically Sortable Identifier) is presented as an alternative to UUID (Universally Unique Identifier). Both are 128‑bit identifiers, but ULID adds a millisecond‑precision timestamp to the random component.

Why not choose UUID

UUID has five versions, each with drawbacks:

Version 1 requires a stable MAC address, which is impractical and vulnerable.

Version 2 replaces part of the timestamp with POSIX UID/GID, inheriting the same issue.

Version 3 uses MD5 hashing and needs a unique seed, leading to possible data‑structure fragmentation.

Version 4 is purely random and provides no additional information.

Version 5 uses SHA‑1 hashing with similar seed‑dependency problems.

Even UUID‑4, while random, still carries a non‑zero collision risk. ULID, by contrast, combines a timestamp (millisecond precision) with 80 bits of randomness, yielding 1.21×10²⁴ possible values per millisecond and effectively eliminating collisions.

ULID Features

ulid() 01ARZ3NDEKTSV4RRFFQ69G5FAV

128‑bit compatibility with UUID.

1.21×10²⁴ unique ULIDs per millisecond.

Lexicographically sortable (alphabetical order).

Encoded as 26 characters (vs. UUID’s 36).

Uses Crockford’s Base32 for efficiency and readability.

Case‑insensitive.

URL‑safe (no special characters).

Monotonic ordering within the same millisecond.

ULID Specification

The current Python implementation (ulid‑py) follows the binary layout shown below.

01AN4Z07BY      79KA1307SR9X4MV3
|----------|    |----------------|
 Timestamp          Randomness
 10 chars            16 chars
  48 bits             80 bits

Components

Timestamp

48‑bit integer.

UNIX time in milliseconds.

Enough range to last until year 10889.

Randomness

80‑bit random number.

Cryptographically strong randomness is recommended.

Sorting

The leftmost characters must appear first; the rightmost characters last, using standard ASCII ordering. Sorting is not guaranteed within the same millisecond.

Encoding

ULID uses Crockford’s Base32 alphabet (0123456789ABCDEFGHJKMNPQRSTVWXYZ), which omits I, L, O, and U to avoid confusion.

0123456789ABCDEFGHJKMNPQRSTVWXYZ

Binary Layout and Byte Order

Components are encoded as 16 octets in network byte order (big‑endian).

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, removing the need for DB‑side key generation.

In distributed environments, substitute UUIDs with globally unique, millisecond‑ordered IDs.

Use the embedded timestamp for time‑based sharding or partitioning.

When millisecond precision is acceptable, sort records directly by ULID instead of a separate timestamp column.

Usage (Python)

Install the library:

pip install ulid-py

Create a new ULID:

import ulid
ulid.new()
#

The timestamp part (48 bits) is derived from time.time() with millisecond precision, while the randomness (80 bits) comes from os.urandom() .

Convert existing UUIDs to ULID:

import ulid, uuid
value = uuid.uuid4()
ulid.from_uuid(value)
#

Create ULID from a specific datetime:

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

Access components of a ULID object:

import ulid
u = ulid.new()
u.timestamp()   # returns Timestamp object
u.randomness()   # returns Randomness object

Source code and further documentation are available at the project's GitHub repository.

distributed systemsPythonUUIDunique identifierULID
Architect's Tech Stack
Written by

Architect's Tech Stack

Java backend, microservices, distributed systems, containerized programming, and more.

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.