UUID v4 Collisions — The Math Reddit Overstates
Junior engineer: “What if two users get the same UUID?” Senior shrugs: “Then we retire.” A comment quotes a scary probability without units. The useful move is to separate version 4 math from schema taste.
What UUID v4 actually contains
A UUID is 128 bits. In version 4, 122 bits are random; some bits encode version and variant. That is still an enormous space: 2^122 possible values.
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
^ version 4
^ variant bits
crypto.randomUUID() in browsers and Node generates this form. Homemade Math.random concatenations are not the same claim, and they are not entitled to the same collision story.
Try generating a handful in a UUID generator and inspect the version nibble (4 in the third group) so docs and mental models match what you actually emit.
Birthday paradox in plain language
Collisions are about pairs among n samples, not “odds the next one hits a specific ID.” Rough intuition:
- Chance that someone shares your birthday in a room rises fast with room size
- Chance that a specific other person matches you stays small
For 122-bit randomness, the n where collision probability becomes non-toy is absurdly large for product databases. Generating a billion UUIDs per second for years still sits comfortably below the scary regime — and no SaaS you are building is doing that by accident.
When people “observe collisions,” investigate the prosaic list first:
- Not actually v4 (sequential counter dressed as UUID)
- Truncation (
slice(0, 8)for “prettier” URLs) - Seeded RNG in tests leaking into shared environments
- Importing the same dump twice
- A buggy client that reuses one cached id
v4 is not a timeline
Created earlier ─✗─> lexicographically smaller v4
Sorting v4 strings by time does not work. UI “order by id” will look random. If you need both uniqueness and roughly time-ordered inserts:
- UUID v7 — time-ordered, still unique
- ULID / KSUID — similar motivation
- created_at column — keep a random PK, sort by timestamp
Choosing autoincrement “because v4 might collide” solves the wrong problem and adds enumeration leaks on public URLs.
Comparing ID schemes on purpose
| Scheme | Collision story | Sortable | Predictable |
|---|---|---|---|
| UUID v4 | Negligible if random | No | No |
| UUID v7 | Negligible | Approx. by time | Partially (time prefix) |
| Bigserial | DB allocates uniquely | Yes | Yes (enumerable) |
| Short nanoid | Depends on length | Optional | Depends |
Short public tokens need explicit length and entropy analysis. Full v4 does not need fear-based shortening. If marketing wants short links, mint a separate slug table — do not amputate the primary key.
How to talk about risk in a design review
- State the generator (
crypto.randomUUID/ database function). - State whether IDs are truncated anywhere in the path to storage.
- State uniqueness constraints and conflict handling.
- If index fragmentation is the worry, discuss v7 — not “collisions.”
- If privacy is the worry, prefer non-enumerable IDs over serial, and still authorize every read.
- Write an example UUID in the RFC so reviewers can see the version nibble.
Also decide whether clients may generate IDs. Client-generated v4s are fine with idempotent POSTs; they are a footgun if every retry invents a new id and creates duplicate business objects.
Takeaway
UUID v4 collision math is a rounding error next to disk, human error, and retry bugs. Do not pick serial IDs because a comment overstated the birthday paradox. Pick serials if you want compact keys and accept enumeration; pick v4 if you want simple unique IDs without coordination; pick v7 if you want uniqueness and nicer insert locality. Generate real v4s when documenting, verify the version bits, and keep probability horror stories out of schema RFCs.