devkult_
tools24converters26
home/blog/uuid-v4-vs-ulid
uuiduliddatabase

UUID v4 vs ULID: Which Should You Use for IDs?

When you need a unique identifier that you can generate anywhere — on the client, in a worker, across services — without coordinating with a central authority, you reach for a random ID. The two most common choices are UUID v4 and ULID. They solve the same problem and look superficially similar, but one of them is sortable and the other will quietly fragment your database index.

What each one is

A UUID v4 is 128 bits, almost entirely random, rendered as 36 characters with hyphens:

f47ac10b-58cc-4372-a567-0e02b2c3d479

A ULID is also 128 bits, but it's split into two parts: a 48-bit millisecond timestamp followed by 80 bits of randomness. It's rendered as 26 characters using Crockford's Base32 (no hyphens, no ambiguous letters like I, L, O, U):

01ARZ3NDEKTSV4RRFFQ69G5FAV
└─ time ──┘└─ randomness ─┘

Both fit in the same 128 bits, so both have effectively zero collision risk for any realistic workload. The difference is what those bits encode.

The key difference: sortability

A UUID v4 has no internal order. Two UUIDs generated a second apart are no more "adjacent" than two generated a year apart — they're random points in a 128-bit space.

A ULID leads with a timestamp, so lexicographic sort order matches creation order. Sort a list of ULIDs as strings and you get them back in roughly the order they were created (down to the millisecond; within the same millisecond, the random component breaks ties). That single property is why people pick ULID.

Why random IDs hurt your database

This is the part that bites teams in production. If you use a UUID v4 as a primary key in a B-tree-indexed table (the default in MySQL/InnoDB and most setups), every insert lands at a random position in the index. The database constantly splits pages and writes to scattered locations, which:

  • thrashes the buffer pool (the page you need is rarely cached),
  • causes page splits and fragmentation,
  • inflates write amplification on disk.

A ULID (or any time-ordered ID) inserts in near-append order — new rows cluster at the "end" of the index, pages fill sequentially, and the working set stays hot. On large, write-heavy tables this is a measurable throughput difference, not a micro-optimization.

If you're stuck with UUIDs and InnoDB, UUID v7 — a newer time-ordered UUID variant — gives you the same sortability benefit while staying in the UUID format. It's worth knowing about, but ULID has had wider library support for longer.

A quick comparison

UUID v4 ULID
Bits 128 128
Length 36 chars (with hyphens) 26 chars
Sortable by time no yes
URL-safe needs care (hyphens ok) yes (Base32)
Index-friendly inserts no yes
Reveals creation time no yes (timestamp is readable)
Ubiquity / tooling everywhere good, slightly less

When to use which

  • Reach for ULID when the ID is a database primary key, when you want naturally chronological ordering "for free," or when shorter, URL-friendly identifiers matter.
  • Stick with UUID v4 when you specifically don't want to leak creation time (a ULID's timestamp is plainly readable), when a downstream system mandates UUID format, or when you value maximum ecosystem compatibility over sortability.
  • Consider UUID v7 if you want ULID's ordering but need to stay in the UUID format for an existing schema or library.

Generate some and see

The fastest way to build intuition is to generate a batch of each and look at them side by side — notice how a column of ULIDs sorts cleanly while UUIDs scatter. The UUID/ULID generator produces both in bulk, entirely in your browser, so you can copy a set straight into a seed script or test fixture.

The short version: same 128 bits, but ULID spends the first 48 of them on a timestamp — and that one decision is what makes it sortable and index-friendly.

More from the blog