UUIDv7 vs ULID: which time-sorted ID should you use?
Short answer
They are close cousins: both are 128-bit IDs that start with a 48-bit millisecond timestamp, so both sort by creation time and both keep database inserts fast. Pick UUIDv7 for database keys: it is an IETF standard (RFC 9562, May 2024), PostgreSQL 18 generates it natively, and it fits every uuid column. Pick ULID when the ID lives mostly as a string and you want 26 characters instead of 36.

Part of the article Pick a UUIDv7 library that keeps order: 10 tested, .NET fails 50%. This page answers one question from it in depth.
On this page · 9 sections
In our test on a 2-million-row SQLite table, the two inserted at the same speed. The only measurable difference was size when stored as text: 167.3 MB for ULID vs 190.6 MB for UUIDv7. Stored as 16 bytes, both came to 143.9 MB.
Key facts
| What | UUIDv7 | ULID | Source (checked Oct 24 2026) |
|---|---|---|---|
| Size | 128 bits | 128 bits | RFC 9562, ULID spec |
| Timestamp | 48-bit Unix ms | 48-bit Unix ms | same |
| Random bits | 74 (plus 6 fixed version/variant bits) | 80 | same |
| Text form | 36 chars, hex with hyphens | 26 chars, Crockford base32 | same |
| Standard | IETF RFC 9562, May 2024 | Community spec on GitHub, 2016 | same |
| Native in PostgreSQL 18 | uuidv7() | no | PostgreSQL 18 docs |
| Insert 2M rows, SQLite, as 16 bytes | ~2.1 s | ~2.2 s | our test, Oct 24 2026 |
| Database size, 2M rows, as text | 190.6 MB | 167.3 MB | our test |
| Database size, 2M rows, as 16 bytes | 143.9 MB | 143.9 MB | our test |
| ULIDs that look like valid UUIDv7 | n/a | 1.56% of 1M | our test |
| npm weekly downloads (Oct 16-22 2026) | uuid 187.3M, uuidv7 2.0M | ulid 7.6M, ulidx 0.5M | npm downloads API |
Download the data (CSV): time and database size for all four storage formats.
The layouts, bit by bit

The first 48 bits are the same idea in both: milliseconds since January 1, 1970, which runs out around the year 10889. After that they split:
- UUIDv7 spends 6 bits on labels: a 4-bit version (
7) and a 2-bit variant. Those labels are what let any UUID parser know what it is holding. The other 74 bits are random or, if the generator wants strict ordering inside one millisecond, partly a counter (RFC 9562, section 6.2). - ULID spends nothing on labels: all 80 remaining bits are random. Its spec defines a "monotonic" mode that increments the random part for IDs made in the same millisecond, and says generation should fail if that part overflows.
ULID came first. Alizain Feerasta published it in the ulid/javascript repo in June 2016, as a fix for random UUIDv4 keys scattering across indexes. RFC 9562, by Kyzer Davis, Brad Peabody and Paul Leach, standardized the same idea as UUIDv7 eight years later.
What we measured
We wrote both generators straight from the specs in Python (no third-party library, so no library quirks) and ran four tables of 2 million rows each in SQLite, the ID as the primary key of a WITHOUT ROWID table, a 40-byte payload per row, on one Windows PC on October 24, 2026.
- Insert speed: a tie. About 2.0 to 2.2 seconds per 2 million rows for all four formats, once ID generation time is subtracted. Both IDs are time-ordered, so every insert lands at the end of the B-tree. The big speed gap is between ordered and random IDs: v4 was 16 times slower in our UUIDv7 vs UUIDv4 test.
- Size as bytes: identical. 143.9 MB each. Sixteen bytes is sixteen bytes.
- Size as text: ULID wins, bytes win more. 26-character ULIDs made a 167.3 MB file (16% bigger than bytes); 36-character UUID strings made 190.6 MB (32% bigger). If your database has a real
uuidorBINARY(16)type, the text-length argument for ULID disappears. - Sorting as strings: both work. We generated 28,572 IDs spread over 200 seconds; sorting the canonical strings gave exactly the time order for both formats.
- A ULID is not a UUIDv7. Read 1 million ULIDs as UUIDs: 25.0% had the RFC variant bits by chance, and 1.56% (1 in 64) also had the version-7 nibble. The other 98.4% are "UUIDs" of no defined version, so PostgreSQL's
uuid_extract_timestamp(), which returns null for anything but versions 1 and 7, cannot read their time, and the 1 in 64 that land on version 1 would give a wrong one.
Which one is winning

ULID has a big installed base: 7.6 million npm downloads a week for ulid, 3.0 million a week on PyPI for python-ulid, and 43.3 million all-time downloads of the Rust ulid crate. But the general UUID libraries now generate v7 too, and they are far larger: the Rust uuid crate has 913 million downloads, npm's uuid 187 million a week. New databases are adding UUIDv7, not ULID: PostgreSQL 18 ships uuidv7() and has no ULID function. Our test of 10 UUIDv7 libraries covers which v7 implementations actually keep order.

When to pick which
- Database primary keys, new project: UUIDv7. Native type, native generator in PostgreSQL 18, every ORM understands it.
- IDs people see, copy or put in URLs: ULID's 26 characters are shorter, case-insensitive and avoid I, L, O and U, so they survive being read aloud. You can also get this from UUIDv7 by displaying it in base32, which some libraries do.
- Existing ULID system: keep it. There is no performance reason to migrate, as our numbers show.
- Mixing them in one column: avoid. The column will accept both, but tools that read timestamps or versions will treat the ULIDs as garbage.
Common misconceptions
"ULID is faster than UUID."
The recordTrue only against random UUIDv4. Against UUIDv7, our 2-million-row test showed no speed difference; both append to the end of the index.
"ULID is just a UUID in a different encoding."
The recordIt is the same size, but not the same bits. A real UUIDv7 has fixed version and variant bits; a ULID does not, so only 1 in 64 ULIDs parse as a valid v7.
"UUIDv7 leaks less than ULID."
The recordBoth reveal when the ID was created to the millisecond. If that matters, use UUIDv4. And neither stops someone reading another user's data if your app has no access checks; guessable IDs are rarely the real bug.
FAQ
What is the difference between UUIDv7 and ULID?
Both are 128-bit IDs that start with a 48-bit Unix timestamp in milliseconds, so both sort by creation time. UUIDv7 is an IETF standard (RFC 9562, May 2024) with 74 random bits and fixed version and variant bits, written as 36 hex characters. ULID is a community spec from 2016 with 80 random bits, written as 26 Crockford base32 characters.
Is ULID faster than UUIDv7?
Not in our test. Inserting 2 million rows into a SQLite table keyed by the ID took about 2 seconds for both, whether stored as 16 bytes or as text. Both are time-ordered, so both append to the end of the index. The difference is size: as text, ULID used 167.3 MB against 190.6 MB for UUIDv7; as 16 bytes, both used 143.9 MB.
Can I store a ULID in a UUID column?
Yes, both are 128 bits, and databases such as PostgreSQL accept any 128 bits in a uuid column. But the result is not a valid UUIDv7: in our sample of 1 million ULIDs, only 1.56% happened to carry the version 7 and variant bits, so functions like PostgreSQL's uuid_extract_timestamp return null for almost all of them.
Should I use ULID or UUIDv7 in 2026?
For new database keys, UUIDv7: it is a standard, PostgreSQL 18 generates it natively, and every uuid column and library understands it. ULID still fits when the ID is mostly a string, for example in URLs or file names, and you want 26 case-insensitive characters instead of 36.
Do ULIDs sort correctly as strings?
Yes, in their canonical uppercase form: in our check of 28,572 IDs over 200 seconds of timestamps, string order matched time order for both ULID and UUIDv7. Mixing upper and lower case breaks it, because ULID decoding ignores case but string sorting does not.
Sources
All checked October 24, 2026.
Specifications
- K. Davis, B. Peabody and P. Leach, RFC 9562: Universally Unique IDentifiers (UUIDs), IETF, May 2024: UUIDv7 layout (section 5.7) and monotonicity methods (section 6.2).
- ULID specification (github.com/ulid/spec, 10.8k stars): 48-bit timestamp, 80-bit randomness, Crockford base32, monotonic mode and overflow rule. Original implementation by Alizain Feerasta in ulid/javascript, created June 13 2016.
- PostgreSQL Global Development Group, UUID functions, PostgreSQL 18: uuidv7(), uuid_extract_timestamp() returning null for versions other than 1 and 7.
- uuid (npm) changelog: RFC 9562 v7 support added in 10.0.0, June 7 2024.
Download counts (pulled Oct 24 2026)
- npm downloads API, last week (Oct 16 to 22 2026): uuid 187,343,522; ulid 7,589,743; uuidv7 1,989,232; ulidx 511,061.
- pypistats: python-ulid: 3,020,009 downloads last week.
- crates.io: uuid 913,378,448 all-time downloads; ulid 43,262,051. (Download counts include CI and bots; see why crates.io download counts are inflated.)
Our benchmark
- Tensorust, Oct 24 2026: spec-faithful UUIDv7 and ULID generators in Python 3 (os.urandom for randomness), SQLite via Python's sqlite3,
CREATE TABLE t(id <BLOB|TEXT> PRIMARY KEY, payload TEXT) WITHOUT ROWID, 2,000,000 rows in 50,000-row batches, journal and sync off, one Windows 11 PC. Insert time estimated as total time minus a separate generation-only pass. Format checks on 1,000,000 ULIDs and 28,572 timestamp-spread pairs. Results in data.csv.
Related on Tensorust
- Parent article: Pick a UUIDv7 library that keeps order: 10 tested. Siblings: UUIDv7 vs UUIDv4, How to extract the timestamp from a UUIDv7.
Next question
Every number on this page links to its source and was checked on Oct 24, 2026. Spot an error or a newer figure? Email matt@tensorust.com and it gets fixed. Related: UUIDv7 generator and decoder, Snowflake ID decoder.
Read next
- Article · Oct 23, 2026Pick a UUIDv7 library that keeps order: 10 tested, .NET fails 50%
- Article · Oct 18, 2026Build a database in Rust: AI passed 866K tests, then failed ROLLBACK
- Explainer · Oct 24, 2026UUID vs NanoID: which ID should you use?
- Explainer · Oct 23, 2026App stuck in "Waiting for Review": how long is normal, and when to worry
Get the next one by email.
What my AI agents tried this week, with the real numbers: what worked, what flopped, what made money. One email when there is something worth reading.
No spam, never sold. Every email has a one-click unsubscribe.