UUID Collisions — Probability Reddit Overstates
A thread claims “UUIDs collide all the time; we switched to autoincrement.” Another claims collisions are impossible. Both oversell. At product scale, your generator and your retry logic matter more than the birthday paradox poster math.
What “safety” means in production
Uniqueness is a system property, not a slogan about 122 random bits:
- Enough entropy in the ID space
- A correct implementation (
crypto.randomUUID, DBgen_random_uuid(), and peers) - A uniqueness constraint where duplicates would corrupt data
- Idempotent APIs that do not invent new failure modes on retry
Without (3), a theoretical collision is irrelevant — you would not notice. Without (2), constraints fire for mundane reasons that look like “UUID bad” in the ticket title.
Where real duplicate-key tickets come from
| Cause | Symptom | Fix |
|---|---|---|
| Client retries POST with a new UUID each time | Duplicate business rows | Idempotency keys |
| Client retries with the same UUID | One row — good — unless server mishandles | Upsert / conflict handling |
| Test fixture UUID hardcoded | Collision across environments | Generate per test |
| Truncating UUID to a “short id” | Actual collisions | Do not truncate primary keys |
| Predictable PRNG on embedded devices | Clusters of repeats | CSPRNG |
These dominate over “two random v4s matched.” When ops sees a unique violation, ask which of the rows above happened before you rewrite the schema.
Scale intuition without fake precision
Random 122-bit IDs (UUID v4) make accidental collision probability negligible for counts humans actually store — millions, billions, even aggressive IoT volumes — if the bits are random. You will hit operational limits (storage, index churn, ops complexity) long before the paradox becomes your quarterly risk.
Worry earlier about:
- Index fragmentation with pure-random primary keys under heavy insert load
- Log verbosity and URL length
- Leaking internal counts with serial IDs (the opposite trade-off)
- Client-side ID generation without idempotency on flaky mobile networks
Generate samples with a UUID tool when documenting APIs or seeding fixtures — never hand-type “random” hex that somehow ships to production.
Design patterns that stay boring
Database primary key — UUID or bigserial both fine; add a unique business key (idempotency_key, external_ref) for the failures you will actually see.
Distributed writers — Prefer UUIDs or Snowflake-style IDs over coordinating a global sequence across regions.
Public resources — UUIDs avoid enumerable /users/1 scraping; still authorize every request. Non-enumerable is not authorization.
v4 versus time-ordered IDs — If insert locality hurts, evaluate UUID v7 or ULID for primary keys while keeping uniqueness guarantees. That is an index performance conversation, not a collision conversation. Do not switch to Math.random string hacks because a meme said UUIDs collide.
Operational checklist
- Use OS/crypto UUID APIs — not homemade concatenations.
- Enforce UNIQUE at the database for anything that must be unique.
- Separate resource id from idempotency key when clients retry.
- Never shorten UUIDs for aesthetics in primary keys; use a separate public slug if you need short URLs.
- Monitor duplicate-key errors; treat spikes as bugs, not destiny.
- Document which version you emit (v4 vs v7) in the API guide so clients stop sorting v4s by time.
Reddit overstates collision drama because collisions make a vivid story. Production uniqueness incidents are almost always prosaic: bad retries, truncated tokens, and test data. Design for those, pick an ID scheme that fits indexes and privacy, and stop debating astronomical probabilities as if they were next quarter’s OKR.