Hash Ids vs Sequential Ids
Random hash/UUID-style identifiers versus monotonic integer primary keys. The choice quietly decides your index health, your URL security, and whether your database hates you at scale.
The short answer
Sequential Ids over Hash Ids for most cases. Sequential IDs win on the thing that actually costs you money: index locality.
- Pick Hash Ids if in a distributed/offline-first system that must mint IDs client-side without coordination, or you'd otherwise leak business volume through guessable URLs and have no slug layer
- Pick Sequential Ids if have a single primary database, care about insert performance, index size, and foreign-key storage — which is almost everyone running Postgres or MySQL
- Also consider: UUIDv7 or ULIDs are the honest middle: globally unique like a hash, time-ordered like a sequence. If you genuinely need decentralized minting, reach for those before raw v4 hashes.
— Nice Pick, opinionated tool recommendations
Index performance is the whole ballgame
This is where random hash IDs earn their reputation. A sequential ID always inserts at the right edge of the B-tree — one hot page, cache-friendly, minimal page splits. A random UUIDv4 inserts everywhere at once, forcing the database to fault cold index pages into memory and split them constantly. On a write-heavy table you'll see write amplification, fragmentation, and an index that's 2-3x larger than it needs to be because random keys don't compress and every foreign key stores 16 bytes instead of 4 or 8. MySQL's InnoDB is especially punished because the primary key IS the clustered row order — random PKs literally scatter your table data on disk. Postgres survives better but still pays. People who claim 'UUIDs are basically free now' tested on empty tables. Wait until the index exceeds RAM. Then they'll send you an apology.
Security and enumeration
Here sequential IDs deserve the beating they get. /invoice/1041 tells an attacker /invoice/1042 exists, and an unprotected handler will hand it over — the classic IDOR. Sequential IDs also leak business intelligence: sign up, note your user ID, sign up tomorrow, subtract, and you've measured someone's growth rate. Hash IDs make enumeration and that German-tank-problem leakage effectively impossible. But — and this is the part people skip — a random public ID is NOT authorization. If your endpoint trusts the ID instead of checking ownership, you have an IDOR with extra entropy. The correct fix isn't 'make IDs unguessable,' it's 'check permissions.' Hash IDs buy you defense-in-depth and non-leaky URLs, which is real and worth having. They do not buy you the right to skip access control. Treat them as obfuscation, not a lock.
Distribution and coordination
Hash IDs have one genuinely unbeatable property: any node, client, or offline device can mint a globally unique ID with zero coordination. No round-trip to a sequence, no central authority, no merge collisions when the laptop reconnects. Sequential IDs need a single source of truth — a sequence, an auto-increment, or a fragile sharded allocator handing out ranges. In multi-master, event-sourced, or sync-heavy systems (think CRDTs, mobile-first apps), that coordination cost is exactly what kills you, and hashes erase it. This is the legitimate reason hash IDs exist — not the URL prettiness, not the vague security hand-waving. If you are NOT distributed, you are paying this tax for a benefit you never use. Most apps have one Postgres. They adopted UUIDs for a coordination problem they don't have, then act surprised when the index doesn't fit in cache.
The verdict in practice
Stop treating this as binary. The mature pattern: sequential (or UUIDv7) primary key internally for index health and clean joins, plus a separate opaque public identifier — a random slug or hashid — for anything exposed in URLs or APIs. You get fast writes, compact foreign keys, AND non-enumerable public surfaces. The cost is one extra indexed column, which is nothing next to a bloated clustered index. If you truly cannot store two columns or you genuinely mint IDs across uncoordinated nodes, use a time-ordered UUIDv7/ULID so you keep insert locality while staying globally unique. Raw UUIDv4 as your clustered primary key is the choice to avoid: it takes the worst of both — fat keys, scattered writes — to solve a coordination problem you probably don't have. Pick sequential at the core. Hide it behind a hash at the edge. Done.
Quick Comparison
| Factor | Hash Ids | Sequential Ids |
|---|---|---|
| Insert/index performance | Random writes scatter the B-tree, cause page splits and bloat | Right-edge inserts, hot page, compact index |
| URL/enumeration safety | Non-guessable, no business-volume leakage | Trivially enumerable, leaks counts via subtraction |
| Storage / foreign-key size | 16 bytes, poor compression, multiplied across every FK | 4-8 bytes, compresses well |
| Distributed/offline minting | Coordination-free, collision-safe across nodes | Needs a central sequence or range allocator |
| Join and debugging ergonomics | Opaque, unsortable, painful to eyeball | Human-readable, naturally ordered, easy to scan |
The Verdict
Use Hash Ids if: You're in a distributed/offline-first system that must mint IDs client-side without coordination, or you'd otherwise leak business volume through guessable URLs and have no slug layer.
Use Sequential Ids if: You have a single primary database, care about insert performance, index size, and foreign-key storage — which is almost everyone running Postgres or MySQL.
Consider: UUIDv7 or ULIDs are the honest middle: globally unique like a hash, time-ordered like a sequence. If you genuinely need decentralized minting, reach for those before raw v4 hashes.
Hash Ids vs Sequential Ids: FAQ
Is Hash Ids or Sequential Ids better?
Sequential Ids is the Nice Pick. Sequential IDs win on the thing that actually costs you money: index locality. Random hash IDs scatter B-tree inserts across the whole index, blow up write amplification, and bloat every foreign key. Use opaque public slugs for URLs if you must — but keep the primary key sequential.
When should you use Hash Ids?
You're in a distributed/offline-first system that must mint IDs client-side without coordination, or you'd otherwise leak business volume through guessable URLs and have no slug layer.
When should you use Sequential Ids?
You have a single primary database, care about insert performance, index size, and foreign-key storage — which is almost everyone running Postgres or MySQL.
What's the main difference between Hash Ids and Sequential Ids?
Random hash/UUID-style identifiers versus monotonic integer primary keys. The choice quietly decides your index health, your URL security, and whether your database hates you at scale.
How do Hash Ids and Sequential Ids compare on insert/index performance?
Hash Ids: Random writes scatter the B-tree, cause page splits and bloat. Sequential Ids: Right-edge inserts, hot page, compact index. Sequential Ids wins here.
Are there alternatives to consider beyond Hash Ids and Sequential Ids?
UUIDv7 or ULIDs are the honest middle: globally unique like a hash, time-ordered like a sequence. If you genuinely need decentralized minting, reach for those before raw v4 hashes.
Sequential IDs win on the thing that actually costs you money: index locality. Random hash IDs scatter B-tree inserts across the whole index, blow up write amplification, and bloat every foreign key. Use opaque public slugs for URLs if you must — but keep the primary key sequential.
Related Comparisons
Disagree? nice@nicepick.dev