Collected · nanoid · MIT

Temporary exhibition · Hall of the Commons

Fair Dice

JavaScript·2017·177 lines·6158 bytes

Curator’s note

A library for generating random strings ought to be four lines, and this one is not, and every line explains why.

The thing it refuses to do is the obvious thing. Pick a random byte, take it modulo the alphabet length, index in. That is what most hand-rolled ID generators do and it is quietly wrong: 256 does not divide evenly by 64, or by 36, or by most useful alphabet sizes, so the first few characters of the alphabet come up slightly more often than the rest. The ID still looks random. It just has less entropy than you were told, forever, in every record you ever wrote.

safeByteCutoff = 256 - (256 % alphabet.length) is the fix. Bytes above that line are thrown away and drawn again, so every character is exactly as likely as every other. Rejection sampling is not clever — it is just the discipline to notice the bias and then to spend real bytes correcting it.

Then look at the branch immediately after. When the alphabet length is a power of two, nothing needs rejecting: the modulo is exact and becomes & mask. So the library carries two implementations, chooses between them once when the generator is built rather than on every character, and the fast path costs nothing at all.

The chunking at the top is the same instinct pointed at a different fact: crypto.getRandomValues refuses requests over 65536 bytes. Not a design choice, just the platform, handled once and never mentioned again.

The whole file is a catalogue of things that go wrong when you generate identifiers casually. It hangs here as an argument that the difference between a snippet and a library is entirely the edge cases.