Choosing an ID Format: UUID, ULID, Snowflake or Auto-increment
Almost every system needs to give things unique IDs, and the choice has consequences that surface much later — in database performance, in whether IDs leak information, and in how easily different services can generate them without colliding. This guide compares the common formats and gives you a way to choose deliberately rather than by habit.
Auto-incrementing integers
The classic database ID: 1, 2, 3, and so on, handed out by the database. They are tiny, fast to index, and naturally ordered by creation. For a single database that is often all you need.
Their weaknesses appear at the edges. Only the central database can mint them, so independent services or offline clients can't generate IDs ahead of time. They also leak information: a competitor can read your invoice number to estimate how many orders you have, and sequential IDs in a URL invite people to guess neighbouring records.
UUID version 4 (random)
A v4 UUID is 122 random bits, usually written as 36 characters. Anyone, anywhere, can generate one with no coordination and effectively zero chance of collision, which makes them ideal for distributed systems, merge-friendly keys and public identifiers that shouldn't be guessable.
The cost is size and ordering. They are far larger than an integer, and because they are fully random they are not time-ordered — inserting random keys into a typical B-tree index scatters writes and can hurt database performance at scale. They also carry no embedded information, which is sometimes a feature and sometimes a limitation.
Time-ordered IDs: UUID v7, ULID and Snowflake
A newer family fixes the ordering problem by putting a timestamp in the leading bits, so IDs generated later sort after earlier ones while still being globally unique. UUID v7 does this within the standard UUID format. ULID does the same in a compact 26-character, URL-friendly, case-insensitive encoding. Snowflake IDs (popularised by Twitter) pack a timestamp, a machine ID and a per-millisecond counter into a 64-bit integer.
Time ordering gives you index-friendly inserts and free chronological sorting, without a central authority. The trade-off is that the creation time is now visible in the ID — usually harmless, occasionally something you'd rather not expose.
What the choice actually affects
Four properties move together as you pick a format, and it helps to weigh them explicitly.
- Coordination: can any node generate IDs alone (UUID/ULID/Snowflake), or only a central database (auto-increment)?
- Ordering: are IDs sortable by creation time (auto-increment, v7, ULID, Snowflake) or not (UUID v4)?
- Size and index cost: integers are smallest; random UUIDs are largest and least index-friendly.
- Information leak: sequential integers reveal volume and invite enumeration; random UUIDs reveal nothing; time-ordered IDs reveal the timestamp.
A reasonable default
If you have one database and don't expose IDs publicly, auto-increment integers are simple and efficient. If IDs are public, or generated by many services, or used as URL slugs, reach for a UUID — v7 or ULID when you want them roughly time-sortable for database efficiency, v4 when you specifically want them to reveal nothing at all.
Whatever you choose, be consistent within a system and avoid mixing formats for the same kind of entity. The worst ID strategy is usually the one chosen by accident.