Methodology & Accuracy

Every generator on this site is a thin interface over one small, pure, automatically tested drawing engine. This page documents how a letter actually gets chosen — where the randomness comes from, how bytes become letters without bias, how pools are built — and what the site deliberately does not do.

This page is the reference. Six guides take one section each and work through it at greater length, with scripted draws and the arithmetic written out: How a random letter is drawn follows a byte through uniformIndex to a letter; Rejection sampling, explained computes the remainder bias and the acceptance rate; How letter pools are built runs each composition stage in turn; Drawing without repeats steps through the partial Fisher–Yates shuffle; Uniform randomness and streaks computes how often repeats and runs should appear; and The Greek alphabet table renders the data the Greek generator draws from. The full list is on the guides index. Every guide, like this page, takes its letters and figures from the engine at build time.

Where the randomness comes from

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. "Cryptographically uniform" on this site means exactly this: cryptographic-quality input bytes, mapped to letters by a construction that provably preserves uniformity.

The engine reaches that source through one injectable function, a ByteSource, whose only job is to fill a buffer with random bytes. The default is crypto.getRandomValues; the tests and the worked examples on this site substitute a scripted source that hands back known bytes. That is how a page can show exactly what the engine does with a given byte — without implying that a live draw is predictable, because the live draw never uses a scripted source.

Rejection sampling, in plain language

A random byte holds 256 equally likely values, but a letter pool holds, say, 26. The tempting shortcut — divide by 26 and keep the remainder — is biased: 256 is not a multiple of 26, so the remainders 0–21 occur ten times among 256 byte values while 22–25 occur only nine, making A–V about 11% more likely than W–Z. This engine instead does what dice players do when a roll doesn’t fit: reroll. It keeps only the minimal bits needed to cover the pool (5 bits for 26 letters, values 0–31), and if the value lands at or beyond the pool size — 26 through 31 — it is thrown away entirely and a fresh value is drawn. Nothing wraps around, so every letter keeps exactly probability 1/26. Rejection wastes a few draws (never more than half, so on average fewer than two per letter) and buys exactness — the uniformity claim rests on this construction, not on statistical testing after the fact.

Masking, on one scripted byte. Byte 200 is 11001000 in binary. The engine keeps the low 5 bits, 01000, which is 8 — below 26, so it is accepted and indexes letter I. The counting source confirms 1 byte asked for, one letter returned.

Rejection, on two scripted bytes. Bytes 255 and 32 mask to 31 and 0. The first lands at or beyond 26 and is discarded without wrapping around; the second is accepted and indexes A. The counting source confirms the engine asked for 2 bytes to return one letter.

Both are build-time draws through the real engine with scripted bytes chosen for illustration. Live draws take their bytes from crypto.getRandomValues.

How pools are built

A draw's pool is composed in a fixed order: alphabet (English 26 or Greek 24) → vowel or consonant filter → custom include list (intersection) → custom exclude list (subtraction) → case expansion. Choosing "both cases" doubles the remaining pool — A–Z then a–z — so mixed case is a genuine 52-entry pool with each form at 1/52, not an uppercase draw with a coin flip after it. Include and exclude lists are validated against the selected alphabet: a character that is not one of its letters is an error, never silently ignored. If the filters leave nothing, the tool says so. Every result reports the exact pool size the draw came from.

The order matters because each stage only ever narrows or re-forms what the previous stage left. The vowel filter applied to the English alphabet leaves A E I O U (5 letters); an include list of "ABCDE" applied on top of it keeps only A E (2 letters — the vowels among the 5 listed), while an exclude list of "E" applied on top of the vowel filter leaves A I O U (4 letters). Case expansion comes last, so "both cases" on the vowel filter gives the 10-entry pool A E I O U a e i o u, uppercase forms first and lowercase after, each at 1/10. Each of those pool sizes is read from the result the engine returns, not typed.

Draws without repeats

"No repeats" switches the engine to sampling without replacement, implemented as a partial Fisher–Yates shuffle driven by the same rejection sampler: each position swaps in a uniformly chosen letter from those not yet drawn. Drawing the whole pool this way yields a uniformly random permutation — every ordering equally likely. Asking for more unique letters than the pool holds is refused with an explanation of the actual limit, because the alternative — quietly repeating or quietly truncating — would be a lie about what you asked for.

Drawing 3 unique vowels, step by step. The pool is A E I O U. The engine copies it into a deck and, at each position, calls uniformIndex with the number of letters not yet drawn, then swaps the letter that far ahead into the current position. With scripted bytes 2, 3, 1:

  1. Position 1: 5 letters remain, byte 2 gives offset 2, letter I.
  2. Position 2: 4 letters remain, byte 3 gives offset 3, letter U.
  3. Position 3: 3 letters remain, byte 1 gives offset 1, letter O.

Result: I U O, drawn from a pool of 5. Draw all 5 vowels this way with scripted bytes 0, 1, 2, 3, 4 and the output is a permutation of the pool — here A I U E O, a build-time draw from those bytes, not a live one. The counting source confirms the engine asked for only 4 of them: the final position has one letter left, and a span of 1 draws no byte. Each of the 120 possible orderings — pool arithmetic again, 5 × 4 × 3 × 2 × 1, the factorial of the pool size — is equally likely. For the 26-letter alphabet the same product runs from 26 down to 1 and gives 403,291,461,126,605,635,584,000,000 orderings; the engine never enumerates them, it simply never favors one.

Both are build-time draws through the real engine with scripted bytes chosen for illustration. Live draws take their bytes from crypto.getRandomValues.

Uniform only — no frequency weighting

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 itself and rendered here verbatim. A consequence worth stating: uniform randomness is streaky. The chance that a 6-letter sequence contains a repeated letter is about 46%, and five vowel draws can come up E every time at odds of exactly 1 in 3,125. Tools that never show streaks are not more random — they are less.

Both figures are pool arithmetic, recomputed at every build. A 6-letter sequence avoids repeats only if each letter misses every letter before it: 26/26 × 25/26 × 24/26 × 23/26 × 22/26 × 21/26 = 53.7%, so the chance of at least one repeat is 46.3% — the "about 46%" above. A run of 5 vowel draws is 5 independent choices from a 5-letter pool, so any one specific run — all E, or any other fixed sequence of 5 — has probability 1 in 55 = 1 in 3,125. The streak is not a defect in the source. A tool that suppressed it would be adjusting its output to look random, which is the opposite of being random.

Worked figures

The figures quoted on this page are not typed in. The build computes each one from the engine's data — pool sizes reported by drawLetters, the alphabet tables — and plain pool arithmetic, then compares the computed value with the value the prose states; a mismatch stops the build. The table shows the arithmetic beside the result and the sentence it supports.

Figure Arithmetic Computed Stated in the prose as
Values in one byte 2^8 256 a random byte holds 256 equally likely values
English pool entries in ENGLISH_ALPHABET, as drawLetters reports it 26 a letter pool holds, say, 26
Byte values divided by the pool 256 = 9 × 26 + 22 quotient 9, remainder 22 256 is not a multiple of 26
Remainders that occur one extra time 0 to 21, each 9 + 1 times 10 times each (A–V) remainders 0–21 occur ten times
Remainders that occur the base number of times 22 to 25, each 9 times 9 times each (W–Z) 22–25 occur only nine
Remainder bias 10/9 − 1 11.1%, about 11% about 11% more likely
Bits covering the pool smallest b with 2^b ≥ 26 5 bits, values 0–31 5 bits for 26 letters, values 0–31
Rejected candidates 26 to 31 6 of 32 26 through 31
Share of bytes discarded 6/32 18.75% never more than half
Acceptance rate 26/32 81.25% the pool is always larger than half the masked range
Expected bytes per letter 32/26 1.23 on average fewer than two per letter
Probability of each letter 1/26 3.85% exactly probability 1/26
Mixed-case pool 2 × 26 52 a genuine 52-entry pool with each form at 1/52
Repeat within a 6-letter sequence 1 − 26/26 × 25/26 × 24/26 × 23/26 × 22/26 × 21/26 46.3% about 46%
5 vowel draws all the same letter (1/5)^5 1 in 3,125 exactly 1 in 3,125

Two consequences of the same arithmetic are worth drawing out. The remainder shortcut's bias is small — 10 occurrences against 9 — but it is systematic: it would favor the same 22 letters on every draw, and no number of draws would average it away. Rejection removes it entirely at a cost of 18.75% of bytes discarded (6 of the 32 masked values) — put the other way, 81.25% of masked values are accepted, 26 of the 32. And because the bit width is minimal, the pool is always larger than half the masked range, so the discarded share is below half for every pool size the engine accepts and the expected number of bytes per letter — here 1.23 — stays below two.

The engine, function by function

The engine exports the drawing functions uniformIndex and drawLetters, plus the default byte source, cryptoByteSource, and the data they work on. Everything a generator page does passes through those drawing functions, and the test suite drives them directly with scripted byte sources in place of the default.

uniformIndex(spanExclusive, source)

Returns one uniformly chosen integer from 0 up to, but not including, spanExclusive, consuming bytes from source. It accepts an integer span from 1 to 256, the number of values one byte can hold; every pool on this site fits, the largest being the 52-entry mixed-case pool. Anything else is refused at once with the error "uniformIndex requires an integer span from 1 to 256". A span of 1 is answered without drawing at all, because a one-entry pool has nothing to choose. A build-time draw through the real engine shows it: a 3-letter draw from a pool restricted to Q, fed a counting scripted source, returns Q Q Q and asks that source for 0 bytes — so for this span a scripted source and crypto.getRandomValues behave identically, because neither is consulted. For every larger span the function computes the minimal bit width covering spanExclusive − 1, masks each byte down to that width, and loops until a masked value falls inside the span — the procedure demonstrated above.

drawLetters(options) — count

count is the only required option: a whole number from 1 to 100. Anything outside that range, or not a whole number, is refused before any byte is drawn, with one message for every such case: "How many: enter a whole number from 1 to 100." The bounds are the engine constants MIN_COUNT and MAX_COUNT; this page prints them from the engine rather than typing them.

alphabet and filter

alphabet selects english26 (26 letters, the default) or greek24 (24 letters). filter then keeps all (the default), vowels or consonants, using the vowel flag stored against each letter in the alphabet table. The English vowel filter leaves A E I O U (5 letters) and the consonant filter the remaining 21; together they account for all 26. The Greek vowel filter leaves Α Ε Η Ι Ο Υ Ω (7 letters) and the consonant filter 17, again summing to the whole alphabet of 24.

include and exclude

include keeps only the letters you list; exclude removes the letters you list. Both are parsed the same way: spaces, commas and semicolons are separators and are ignored, case is ignored, and every remaining character must be a letter of the selected alphabet — otherwise the whole call is refused with a message naming the offending character. They apply after the filter and before case expansion, which is why an include list cannot add a consonant back into a vowel pool: intersection only ever narrows. Listing every vowel in the exclude list under the vowel filter empties the pool, and the engine says so rather than drawing from nothing.

letterCase

letterCase is upper (the default), lower or both, and it is the last stage, applied after the filters have fixed which letters are in. upper and lower change the form of each letter without changing the count: the scripted bytes 0, 7, 25 that yield A H Z in uppercase yield a h z in lowercase, each from a pool of 26. both appends the lowercase forms after the uppercase ones, so the full alphabet becomes a 52-entry pool in which each form has probability 1/52. Under unique, A and a are distinct entries and can both appear in one batch.

unique

unique switches to sampling without replacement. The engine copies the pool into a deck and, for each position in turn, swaps in a uniformly chosen entry from the part of the deck not yet drawn — the partial Fisher–Yates shuffle stepped through above, with uniformIndex called on a span that shrinks by one at each step. Before any of that, the count is checked against the pool: asking for 6 unique letters from the 5-vowel pool is refused with "Cannot draw 6 letters without repeats from a pool of only 5 — allow repeats, lower the count, or widen the pool." The refusal states the actual pool size, so the fix is always visible.

What a rejected call says

drawLetters never throws. Every refusal is a Result carrying a code and a plain-text message, nothing is silently corrected, and no byte is drawn for a call that fails validation. The messages below are the engine's own, produced at build time by calls constructed to fail:

Call Message
count 101 (or 0) How many: enter a whole number from 1 to 100.
include "A1" The include list contains “1”, which is not a letter of the selected alphabet.
Greek alphabet, exclude "Q" The exclude list contains “Q”, which is not a letter of the selected alphabet.
vowels, exclude "AEIOU" The selected filters leave no letters to draw from — relax the filters.
6 unique vowels Cannot draw 6 letters without repeats from a pool of only 5 — allow repeats, lower the count, or widen the pool.

The Greek alphabet data

The Greek generator draws from a hand-checked table of the 24 modern Greek letters, alpha through omega, each with its uppercase form, lowercase form, and name. Lowercase sigma uses the medial form σ (the final form ς appears only at word ends, and the engine draws letters, not words — though typing ς in an include/exclude list is accepted as sigma). The automated tests assert the table's completeness: exactly 24 entries, the full canonical name sequence, distinct glyphs, and no Latin look-alike characters standing in for Greek ones. The alphabet table on the Greek page renders from this same data.

The include and exclude parser accepts Greek letters in either case, and it accepts final sigma: an include list of "ς" under the Greek alphabet yields a pool of Σ (1 letter), or σ when the pool is lowercase — the medial form, as the table stores it. A Latin letter in a Greek list is refused with the message shown above, because Q is not a letter of the selected alphabet.

Worked examples can't drift

The example draws quoted in page copy are not typed in by hand: at build time, each page runs the real engine with a scripted byte source (for instance bytes 0, 7, 25 → A H Z), and the resulting letters are printed into the prose. If the engine's mapping ever changed, the copy would change with it — or the build would fail — so the documentation cannot silently disagree with the tool.

This page holds itself to the same rule twice over. Its letters come from scripted draws — the bytes 0, 7, 25 above really do produce A H Z at build time — and its figures come from pool arithmetic that is checked against the prose before the page is written out. A page that said "about 11%" while the arithmetic produced something else would not build.

Privacy: nothing leaves the browser

The generators are static pages with client-side JavaScript. Your draws — the options you set and the letters you get — exist only in your browser tab: there is no server to send them to, no logging, and no storage; closing the page destroys the session. You can verify this yourself: open your browser's developer tools → Network tab and generate — you will see no request carrying your draw.

Tested, and fixed with tests

The engine's test suite drives the sampler with scripted byte sources to pin the exact byte-to-letter mapping (including the rejection path), exercises every pool composition (vowels ∩ exclusions, include lists, mixed case), covers the no-repeat exhaustion errors and count bounds, and locks the Greek alphabet data. If you believe a draw was outside the pool or a filter misbehaved, that is precisely the report we want — see the contact page. Confirmed issues are fixed in the engine and locked in with a new test so they cannot recur silently.

Last reviewed . Every letter and figure on this page was recomputed from the engine by the build that produced it. Draws happen locally in your browser and are never transmitted.