03 / GUIDES
UUID vs ULID: choosing identifiers for your application
UUID v4 versus v7, ULID structure and sortability, what random identifiers do to a B-tree index, collision probability, exposure in URLs, and generating identifiers locally.
What an application identifier must do
A generated identifier has one job—be unique—plus a few it picks up along the way: generate anywhere without coordination, store compactly, sort predictably, and leak nothing embarrassing. UUID and ULID both clear the uniqueness bar; they differ on everything after it.
- No coordinationDecentralized generation: any service, worker, or browser tab can mint an identifier without asking a central counter, which is what makes both formats fit distributed systems.
- Same 128 bits128 bits either way: a UUID is a 128-bit value written as 36 hexadecimal characters with hyphens; a ULID carries the same 128 bits as 26 base32 characters.
- Layout is the differenceThe real question is layout: which bits are random and which encode time decides how the identifier behaves in an index and what it reveals.
UUID versions: v4 is random, v7 is time-ordered
RFC 9562 defines several UUID versions, but two dominate application work. Version 4 fills 122 of the 128 bits with randomness and embeds nothing else; version 7 leads with a 48-bit Unix timestamp in milliseconds and fills the remaining 74 bits with randomness.
- v4: pure randomv4 is the privacy-maximal choice: the value says nothing about when or where it was made, and 122 random bits is more uniqueness than any application will ever spend.
- v7: time firstv7 sorts by creation time to millisecond precision, so identifiers generated later sort after earlier ones—the property databases want from a primary key.
- Skip v1Older versions are legacy: v1 embedded a MAC address and a clock value, leaking both; v3 and v5 are name-based hashes for deriving stable IDs, not fresh randomness.
ULID: sortable and compact as text
A ULID is 26 characters of Crockford base32: the first 10 characters encode a 48-bit millisecond timestamp, the remaining 16 encode 80 random bits. The alphabet deliberately excludes I, L, O, and U, so identifiers survive being read aloud or typed from a screenshot.
- String sort = time sortLexicographic order is time order: sorting ULIDs as plain strings sorts them by creation time with no parsing—useful in log lines, file names, and key-value stores.
- 26 safe charactersCompact and URL-safe: 26 case-insensitive characters with no hyphens or symbols, shorter than a UUID’s 36 and safe in any path segment or query parameter.
- Monotonic per millisecondWithin one millisecond, ordering comes from the random part; generators that increment it monotonically—as the Toolars batch generator does—keep even same-millisecond identifiers in generation order.
Random IDs fragment your indexes
A B-tree index is ordered, and inserting a random UUID v4 writes to a random leaf every time: pages split, the buffer cache churns, and the index swells with dead space. Time-ordered identifiers append near the right edge of the index, turning inserts into the B-tree’s best case.
This is why the choice belongs at schema design time, not after the first slow quarter: migrating a primary key means rewriting every row and every foreign key that references it. If the table will stay small, v4’s disorder never becomes visible; if it will grow into hundreds of millions of rows under constant writes, the ordered formats pay rent from day one.
- Random scattersWith v4 as a primary key, insert-heavy tables show more page splits, lower fill factors, and measurably worse write throughput than with an ordered key of the same size.
- Ordered appendsv7 and ULID restore locality while keeping generation decentralized—no sequence, no coordination, and still no prediction of other rows’ IDs.
- Measure the tradeThe trade is real but modest: 128 bits is double a bigint’s storage, and time-ordered keys concentrate today’s writes on the index’s right edge, which only matters at very high insert rates.
Collisions, URLs, and what an ID reveals
Collision math first: with 122 random bits in v4, generating a billion identifiers leaves a collision probability near one in 10^18—effectively never. ULID’s 80 random bits guard a different budget: identifiers created within the same millisecond.
Paste any suspicious value into the validator: it recognizes the canonical 36-character UUID shape with its RFC 9562 variant and version nibble, the 26-character ULID alphabet with its overflow rule, and it reports the embedded timestamp for time-ordered formats—so a value from a log tells you what it is before you build on it.
- Probability is negligibleNeither format is a security boundary: an identifier in a URL is fine for addressing, but anyone who sees it can quote it—authorization must come from access checks, not from ID opacity.
- IDs are not secretsv7 and ULID leak creation time by design; the validator extracts it openly. That is usually harmless metadata, but decide it consciously for public-facing objects.
- Time is visibleSequential database IDs leak volume and growth rate; random and time-random identifiers reveal nothing about how many records exist.
Generate and validate without a server
Identifier generation needs exactly one scarce resource—good randomness—and the browser already has it. The workspace draws from crypto.getRandomValues, generates batches entirely in the tab, and never transmits a value.
- 1–100 per batchChoose UUID v4, UUID v7, or ULID and generate 1 to 100 per batch; v7 and ULID batches increment the random field monotonically, so a whole batch stays in generation order.
- Case and exportOutput case is a display choice: canonical, uppercase, or lowercase, with copy-all plus newline-delimited text and CSV downloads that record each identifier’s embedded time.
- Instant validationValidation answers structure questions instantly: length, alphabet, UUID version and variant, nil and max special values, ULID overflow, and the embedded timestamp in UTC.
ULID is base32; your tokens are probably Base64.
Byte-to-text encodings sit underneath every identifier and token—learn what they cost and where they earn their keep.