Meet ULID: The Sortable UUID Alternative You Should Consider

    February 5, 2024
    9 min read
    Explainer
    Technical introduction
    uuid
    innovation

    If you’ve worked with UUIDs, you probably love their uniqueness — and hate their randomness.

    Enter ULID: a lexicographically sortable, URL-friendly, and high-performance alternative to UUIDs. In distributed systems, where sort order and readability matter, ULIDs can be a huge win.

    Let’s take a closer look at what they are, how they work, and where you might want to use them.


    🔍 What Is a ULID?

    ULID stands for Universally Unique Lexicographically Sortable Identifier. It was introduced by Alizain Feerasta in 2016 as an improvement over UUIDv4, addressing two major limitations:

    • Lack of natural sort order
    • Low human readability

    A ULID is a 128-bit identifier — just like a UUID — but structured in a way that makes it sortable by creation time.

    ULID Format (128 bits total):

    • 48 bits for timestamp (in milliseconds since Unix epoch)
    • 80 bits for randomness

    Example ULID:

    code
    01H5ZYPW4ZYZNM8B8X7TPZBZV7

    🧪 Why ULID Instead of UUID?

    ✅ Lexicographical Sortability

    ULIDs are designed so that newer IDs are greater than older ones when sorted as strings.

    This is perfect for:

    • Logs
    • Time-series data
    • Row-level insert order
    • Pagination without external timestamps

    ✅ URL-safe, No Hyphens

    ULIDs are encoded in Crockford's Base32, making them:

    • Shorter than UUID strings
    • Easier to copy/paste
    • Safe for URLs and filenames

    ✅ Client-Side Friendly

    You can generate ULIDs in the browser, in the DB, or on edge nodes — no need for coordination or clocksync with a central server.


    ⚖️ ULID vs UUID: A Quick Comparison

    FeatureUUIDv4ULID
    Uniqueness✅ Very high✅ Very high
    Sortable❌ No✅ Yes
    Time Encoded✅ (48-bit millis)
    URL-safe❌ (hyphens)
    EncodingHexCrockford Base32
    Human-friendly
    Native DB Support✅ (Postgres)❌ (store as CHAR)
    Random bits122 bits80 bits

    🧰 When to Use ULIDs

    ULIDs shine in systems where time-based order matters but you still want global uniqueness.

    🔄 Great for:

    • Event-sourced systems
    • Append-only logs
    • Audit trails
    • Message queues (e.g., Kafka keys)
    • Primary keys where sort order improves performance

    🚫 Avoid if:

    • You need cryptographic-level randomness (UUIDv4 might be better)
    • You're relying on native DB UUID support
    • Millisecond granularity isn’t precise enough (use Snowflake-style IDs)

    💻 ULID in Code

    Python

    python
    from ulid import ULID
    
    id = ULID()
    print(str(id))  # e.g. 01H5ZYPW4ZYZNM8B8X7TPZBZV7

    Install with:

    bash
    pip install ulid-py

    JavaScript

    js
    import { ulid } from 'ulid';
    
    const id = ulid();
    console.log(id); // e.g. 01H5ZYPW4ZYZNM8B8X7TPZBZV7

    Go

    go
    import "github.com/oklog/ulid/v2"
    
    id := ulid.Make()
    fmt.Println(id.String())

    🧬 Internals: How It Works

    • The timestamp ensures sortability
    • The random component ensures uniqueness within the same millisecond
    • The final result is 26 characters long

    ULID Collision Risk?

    It’s astronomically low.

    With 80 bits of randomness per millisecond, ULIDs can safely generate millions per node per second without fear of collision.


    🗃️ ULIDs in Databases

    PostgreSQL

    • No native ULID type
    • Store as CHAR(26) or BYTEA
    • Index performance is good if consistently generated

    MySQL

    • Use CHAR(26) or BINARY(16)
    • Consider prefix indexing for faster queries

    > Tip: ULIDs work best when you don’t truncate or pad inconsistently


    🔮 ULID vs KSUID vs Snowflake

    ULID isn’t alone. Here’s how it stacks up:

    FormatSortableSizeBaseDesigned for
    ULID26Base32General usage
    KSUID27Base62High-entropy tracing
    Snowflake64-bitBase10Centralized, Twitter
    UUIDv736HexRFC standard

    📈 Adoption in the Wild

    • PlanetScale uses ULIDs for row-level replication
    • FaunaDB and Firestore explored ULIDs for key generation
    • Many startup stacks adopt ULIDs for ID-as-primary-key strategies

    🔐 Are ULIDs Secure?

    ULIDs aren’t cryptographically secure, but they are:

    • Random enough for most systems
    • Impossible to guess without observing generation
    • Safe to expose in public APIs

    If you need tamper-proof IDs, use JWTs, signed tokens, or CIDs instead.


    Final Thoughts

    UUIDs got us far — but for many use cases, ULIDs are a better default.

    They’re globally unique, time-sortable, URL-safe, and pleasant to work with.

    If you’re tired of uuid.uuid4() giving you unpredictable chaos in your logs and databases, maybe it’s time to give ULIDs a spin.

    🧭 The future of identifiers might just be base32.

    Summary

    This article introduces ULIDs (Universally Unique Lexicographically Sortable Identifiers) as a powerful, sortable alternative to UUIDs. It explains how ULIDs work, why they matter, and where they outperform traditional identifiers in distributed systems.

    TLDR;

    ULIDs are sortable, URL-safe identifiers designed for distributed systems — a modern alternative to UUIDv4.

    Key takeaways:

    • ULIDs are lexicographically sortable by timestamp, making them ideal for logs and event streams
    • They use 128 bits of entropy (like UUIDs), with better readability and compact encoding
    • Great for microservices, databases, and anything that needs ordered identifiers

    If you’ve ever wished your UUIDs were easier to debug and more logically ordered, ULIDs are worth a serious look.

    Cookie Consent

    We use cookies to enhance your experience on our website. By accepting, you agree to the use of cookies in accordance with our Privacy Policy.