How a random letter is drawn

Every generator on this site ends in the same four steps: ask the browser for a random byte, keep only the bits that can address the pool, discard the value if it lands at or beyond the pool size, and otherwise read the letter at that index. This guide walks through those steps with the real engine. Every letter, bit count and probability below is computed when the page is built — the worked draws use scripted bytes so the page is reproducible, while the live tools draw their bytes from crypto.getRandomValues — and nothing is typed in by hand. The methodology page states the guarantees; this guide shows the mechanism, and the other guides take each step further.

Step one: a byte from the browser’s cryptographic source

The engine does not invent randomness in JavaScript. It is written around a byte source — a function that fills a buffer with random bytes — and the default source is the Web Crypto API’s crypto.getRandomValues, the same operating-system-backed source browsers use for encryption keys and secure tokens. The engine never touches Math.random(), which is a convenience generator with no uniformity or unpredictability guarantees. A byte holds 256 equally likely values, 0 through 255, and the engine asks for exactly one byte per attempt: every pool on this site fits within a byte, the largest being the 52-entry mixed-case pool. Because the source is a parameter, the same engine can be driven by a scripted list of bytes — which is how the tests pin the byte-to-letter mapping and how this page produces every example below.

Step two: keep only the bits that cover the pool

A pool of 26 letters needs indexes 0 through 25. The engine works out the smallest number of bits whose values can cover that range: for 26 letters that is 5 bits, because 5 bits hold 32 values (0–31) while 4 bits would hold only 16, too few. It then masks the byte, keeping its low 5 bits and ignoring the rest. Fed the scripted byte 200 — 11001000 in binary — the engine keeps its low 5 bits, 01000, which is 8, and index 8 of the uppercase alphabet is I. The scripted byte 32 masks to 0 and so produces A, exactly as byte 0 does — the high bits are never consulted. (These are scripted bytes for illustration, as throughout this page; the live generators draw theirs from crypto.getRandomValues.) Masking is a plain truncation, not a division, and it keeps all 32 masked values equally likely: each one is reached by exactly 8 of the 256 possible bytes.

Step three: reject anything at or beyond the pool size

The masked value runs from 0 to 31, but there are only 26 letters. The values 26 through 31 — 6 of the 32 possibilities — point past the end of the pool. The engine neither wraps them around nor scales them down: it discards the byte entirely and draws a fresh one until a value lands inside the pool. Two scripted demonstrations, run by the engine as this page was built: fed the bytes 26, 3, it rejects 26, accepts the next byte as index 3, and returns D after consuming 2 bytes. Fed 31, 255, 10, it rejects 31, masks 255 down to 31 and rejects that too, then accepts index 10 — K, 3 bytes consumed.

The cost is small and bounded. Each attempt succeeds with probability 26/32 = 81.25%, so a redraw is needed 18.75% of the time and a letter costs 1.23 bytes on average. A run of 2 rejections, as in the second demonstration, happens with probability (6/32)2 = 3.52%. Because the bit width is minimal, the pool always fills more than half of the masked range — any pool of 16 or fewer would have fitted in 4 bits or fewer — so acceptance never falls to half and the expected bytes per letter stay below two for every pool.

Why go to this trouble instead of wrapping the byte around? Taking the remainder on division by 26 is biased because 256 is not a multiple of 26: 256 = 9 × 26 + 22, so the remainders 0–21 (A–V) each occur 10 times among the 256 byte values while 22–25 (W–Z) occur only 9 times — making A–V about 11% more likely than W–Z. Rejection removes that bias completely rather than shrinking it; the uniformity claim rests on this construction, not on statistical testing after the fact. The rejection sampling guide works through the arithmetic for every pool.

Step four: the index becomes a letter

An accepted value is an index into the pool, which the engine holds in alphabet order: index 0 is A and index 25 is Z. The scripted bytes 0, 7 and 25 therefore produce A H Z — the same example the methodology page quotes, because both pages run the same engine. Each drawn letter carries its character and its name (H for the second letter here; Omega for the Greek Ω), and the result object reports the exact pool the draw came from, its size, and the word “uniform” as its distribution — the only value that field can take. The result box on every generator prints that pool size, so you can see precisely what “equally likely” applied to: with a pool of 26, each letter had probability 1/26.

The same four steps for every pool

The pipeline never changes with the pool; only the numbers do. The pool is composed first — alphabet, vowel or consonant filter, include and exclude lists, case expansion, in that order, as the pool composition guide shows — and the sampler is handed its size. Each row below is pool arithmetic from that size.

Pool Size Bits kept Masked values Rejected values Acceptance Bytes per letter, on average
English, uppercase 26 5 0–31 (32) 26–31 (6) 81.25% 1.23
English vowels 5 3 0–7 (8) 5–7 (3) 62.5% 1.6
Greek, uppercase 24 5 0–31 (32) 24–31 (8) 75% 1.33
English, both cases 52 6 0–63 (64) 52–63 (12) 81.25% 1.23

Scripted demonstrations for each: the vowel pool is 5 letters (A E I O U), so the engine keeps 3 bits and rejects 5 through 7; fed the bytes 7, 2, it discards the first and returns I after 2 bytes. The Greek pool is 24 letters, so byte 23 is the last entry, Ω (Omega), while the bytes 24, 0 reject the first and return Α (Alpha). The mixed-case pool is 52 entries addressed by 6 bits; the bytes 0, 26, 51 produce A a z, and the bytes 63, 51 reject the first and return z after 2 bytes.

Mixed case is often misread. Choosing both cases does not draw an uppercase letter and then flip a coin for its case. The pool itself is 52 entries — A–Z followed by a–z — so byte 26 is the first lowercase entry, a, and byte 51 is z. Each form is drawn directly with probability 1/52, the reported pool size is 52, and in no-repeat mode A and a are distinct entries, so up to 52 letters can be drawn without a repeat. The uppercase & lowercase generator draws from this pool.

What “cryptographically uniform” means here — and what it does not

It means two things, and only two. The input bytes are of cryptographic quality, because they come from crypto.getRandomValues; and the construction that maps them to letters — minimal bits, then rejection — provably preserves uniformity, so every letter in the pool has exactly the same probability. The claim rests on that construction, not on counting letters after the fact and finding the counts roughly even. That is also where the tool stops. Every letter in the active pool is exactly equally likely — the engine never weights draws by English text frequency or any other distribution. That sentence is exported from the engine source and rendered here verbatim; there is no weighting option, and no page on this site will suggest one. The site claims uniform letters from a cryptographic byte source and nothing more; the methodology page is the complete statement of what it does and does not promise.

How to read a result — and two common misreadings

A draw asks for between 1 and 100 letters, and with repeats allowed each position is an independent run of the four steps above. The result reports the letters and the pool size they came from — the number to read, since each position carried probability 1 divided by that size. The first misreading is the one just covered — treating mixed case as a coin flip after a draw. The second is treating a streak as a fault. Uniform randomness is streaky. The scripted 6-byte draw K K C Z K G contains K 3 times, and nothing about it is unusual: the chance that the next draw repeats the previous one is 1 in 26 every time, and the chance that a 6-letter sequence from this pool contains at least one repeated letter is 1 − ∏(1 − k/26) for k from 0 to 5, about 46%. Likewise 5 vowel draws can come up E every time — scripted here as E E E E E — at odds of exactly 1 in 3,125, since each is a 1-in-5 event and 55 = 3,125. Tools that never show streaks are not more random — they are less. If distinct letters are what you need, the no-repeat mode samples without replacement, described in drawing without repeats; the streak arithmetic for every pool is in uniform randomness and streaks.

Why the examples on this page cannot drift

None of the letters above were typed. When the site is built, this page calls the same drawLetters and uniformIndex functions the generators run in your browser, hands them scripted byte sources, and prints what comes back; the bit counts, rejected ranges, acceptance rates and probabilities are pool arithmetic computed alongside them. If the engine’s mapping ever changed — a different bit rule, a different pool order — the letters and figures here would change with it, or the build would fail. The automated tests pin the same mapping independently, rejection path included. The main generator runs that engine live; this page is the same engine shown working in slow motion.

Frequently asked questions

Does the generator use Math.random()?

No. Draws use the Web Crypto API (crypto.getRandomValues), the same operating-system-backed source browsers use for encryption keys and secure tokens. The engine never touches Math.random(), which is a convenience generator with no uniformity or unpredictability guarantees.

What happens to a byte that is rejected?

It is thrown away entirely and a fresh byte is drawn. With a 26-letter pool the engine keeps 5 bits (values 0–31) and rejects 26 through 31 — 6 of 32 masked values, so a redraw is needed 18.75% of the time. Nothing wraps around, which is exactly why every letter keeps probability 1/26.

Why not just take the byte modulo 26?

Because 256 is not a multiple of 26: 256 = 9 × 26 + 22, so remainders 0–21 (A–V) each occur 10 times among the 256 byte values while 22–25 (W–Z) occur only 9 times. That makes A–V about 11% more likely than W–Z. Rejection sampling removes the bias completely instead of shrinking it.

Is "both cases" an uppercase draw followed by a coin flip?

No. Choosing both cases builds a genuine 52-entry pool — A–Z followed by a–z — addressed by 6 bits, so each form is drawn directly at 1/52. The result reports a pool size of 52, and in no-repeat mode A and a are distinct entries.

Why did the same letter come up several times in a row?

Because uniform randomness is streaky. Each draw is independent, so the chance that the next letter repeats the previous one is 1 in 26 every time, and a 6-letter sequence from the 26-letter pool contains at least one repeated letter with probability 1 − ∏(1 − k/26) for k = 0…5, about 46% — pool arithmetic, not a measurement. Tools that never show streaks are not more random — they are less. If you need distinct letters, switch on "no repeats".

Are the worked examples on this page real draws?

They are real engine runs made at build time with a scripted list of bytes, so the page is reproducible and the examples cannot drift from the tool; the live generators draw their bytes from crypto.getRandomValues instead. If the engine's mapping ever changed, these letters would change with it or the build would fail.

Uniform means uniform: every letter in the active pool is drawn with exactly the same probability — never weighted by English usage. Draws happen locally in your browser and are never transmitted. Details on the methodology page.