Stop Using Math.random() for IDs: crypto.randomUUID()
Somewhere in every codebase lives a homemade ID generator: Math.random().toString(36).slice(2), or a Date.now() concatenation, or that copy-pasted pseudo-UUID snippet with the regex. They all share two problems - and the platform solved both.
crypto.randomUUID();
// "d8f2c4aa-8f13-4c9b-a2e1-9b6f3d7a1c42" - RFC 4122 v4, done
What's actually wrong with Math.random()
Predictability: Math.random is a fast statistical PRNG, not a cryptographic one. Its internal state can be recovered from observed outputs, making the "random" tokens guessable. The moment an ID doubles as a secret - reset links, invite codes, order lookups - that's a vulnerability, not a style issue.
Collisions: toString(36).slice(2) gives you roughly 10-11 base-36 characters of a float's precision - and birthday math is brutal. Generate a few million and duplicates become likely; I've watched an analytics pipeline dedupe itself into wrong numbers exactly this way. A v4 UUID has 122 random bits; collision odds are lottery-times-lottery territory.
For tokens and codes: getRandomValues
randomUUID has a fixed format. When you need a short code, PIN or token in a custom alphabet, drop one level down to the same CSPRNG:
function randomCode(length = 8, alphabet = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789') {
const bytes = crypto.getRandomValues(new Uint8Array(length));
return [...bytes].map(b => alphabet[b % alphabet.length]).join('');
}
randomCode(); // "K3TQ7XNM" - unambiguous alphabet (no O/0, I/1)
(Pedantic footnote: the modulo introduces a tiny bias unless the alphabet length divides 256; for security-critical codes use rejection sampling. For invite codes, nobody will ever measure it.)
The gotchas that actually bite
- Secure context required: randomUUID exists only on HTTPS and localhost. A plain-HTTP staging box mysteriously lacking crypto.randomUUID has confused many a deploy evening. getRandomValues works everywhere.
- Support: randomUUID is Chrome 92+, Firefox 95+, Safari 15.4+, Node 16.7+ (on the crypto global in recent Node). getRandomValues is ancient and universal.
- UUIDs are not secrets by themselves: they're unguessable, but if you use them as capability tokens, still compare them server-side with hash_equals and treat them like credentials.
Where I use each: randomUUID for client-generated entity IDs (optimistic UI inserts, file upload correlation, idempotency keys), randomCode for anything a human types. Math.random keeps exactly one job: visual jitter in animations, where predictability costs nothing.
Full-stack web developer sharing practical tutorials and building tools that ship.
Got something on your mind?
My inbox is open - no forms disappearing into the void here.
- Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
- Hire me for a project Have something custom in mind? Let's talk scope and timelines.
- Product support Bought something here? I'll help you get it running.
I usually reply within 1-2 business days.