How to Generate Random Letters and Character Sets: Combinatorics, Word Games, and Cryptography
By Khizar Tahir · Founder & Developer, Casetivo
From setting up daily word puzzles, generating CAPTCHA verification challenges, simulating DNA sequence variants, to initializing deterministic random test strings, generating individual letters from an alphabet is a foundational primitive across software engineering.
Yet like many seemingly simple tasks, naive implementations often introduce hidden biases, character encoding corruptions, and distribution skews. Picking random characters involves more than calling String.fromCharCode(65 + Math.floor(Math.random() * 26)). Real-world applications demand strict case normalization, language-specific vowel/consonant distribution weightings, distinct delimiter handling, and cryptographically secure entropy pools.
In this deep dive, we will examine the combinatorial mathematics behind character generation, explore Unicode code point boundaries, dissect weighted frequency models (such as Scrabble letter distributions), and demonstrate how our client-side Random Letter Generator provides fast, unskewed character synthesis.
1. The Anatomy of Character Representation: ASCII vs. Unicode
Before writing letter-generation algorithms, software developers must understand how characters are physically represented in modern memory architectures:
+--------------------------------------------------------------------------+
| Character Set & Encoding Structure |
+--------------------------------------------------------------------------+
| |
| [Standard ASCII] (0 - 127) |
| - Uppercase Latin: [65 - 90] ('A' to 'Z') |
| - Lowercase Latin: [97 - 122] ('a' to 'z') |
| |
| [Extended Latin & Diacritics] (128 - 255) |
| - Accents, umlauts, cedillas: [À, é, ñ, ü, ß] |
| |
| [Unicode BMP (Basic Multilingual Plane)] (U+0000 - U+FFFF) |
| - Greek, Cyrillic, Arabic, Hebrew, Devanagari, CJK |
| |
| [Surrogate Pairs & Astral Planes] (> U+FFFF) |
| - Emoji, historical scripts (requires 2 UTF-16 code units in JS) |
| |
+--------------------------------------------------------------------------+
The Pitfall of JavaScript UTF-16 Indexing
JavaScript strings are internally indexed as sequences of 16-bit code units (UTF-16). For standard Latin letters (A-Z, a-z), each character corresponds to exactly one 16-bit code unit, and character codes match ASCII standard offsets.
However, when generating multi-lingual alphabets (such as Cyrillic А-Я or Greek Α-Ω), naive byte indexing breaks characters into invalid surrogate fragments. Robust letter generators operate either on explicitly defined string candidate sets or utilize String.fromCodePoint() rather than legacy String.fromCharCode().
2. Uniform vs. Natural Language Weighted Distributions
When a user requests random letters, their intent dictates the required statistical distribution:
Mode A: Mathematically Uniform (Cryptographic/Combinatorial)
In uniform distribution, every candidate letter in the chosen alphabet has an identical probability:
$$P(L) = \frac{1}{|A|}$$
For the standard 26-letter English alphabet, every letter has exactly $\frac{1}{26} \approx 3.846%$ probability of being selected. This is the optimal configuration for:
- Random acronym generation
- Cryptographic verification tokens and license keys
- Statistical Monte Carlo testing of text parsing pipelines
Mode B: Natural Language Frequency Weighting (Games & Word Generation)
In natural English text, letters do not appear with equal frequency. The letter E appears approximately $12.7%$ of the time, while Z appears less than $0.07%$ of the time.
If you generate 10 uniform random letters to build a word game (like Scrabble or Boggle), players will frequently receive unplayable hands filled with strings like Q-X-Z-J-K-V-W-B-P-M without a single vowel.
To address this, word games utilize weighted bag models. Here is the classic English Scrabble frequency distribution:
+--------------------------------------------------------------------------+
| English Language Letter Frequency Matrix |
+--------------------------------------------------------------------------+
| Letter | English Text Frequency | Scrabble Tile Count | Point Value |
+---------+------------------------+---------------------+-----------------+
| E | 12.70% | 12 | 1 |
| T | 9.06% | 6 | 1 |
| A | 8.17% | 9 | 1 |
| O | 7.51% | 8 | 1 |
| I | 6.97% | 9 | 1 |
| N | 6.75% | 6 | 1 |
| S | 6.33% | 4 | 1 |
| H | 6.09% | 2 | 4 |
| R | 5.99% | 6 | 1 |
| Z | 0.07% | 1 | 10 |
| Q | 0.10% | 1 | 10 |
+--------------------------------------------------------------------------+
3. Implementing High-Entropy Letter Generation in TypeScript
To generate unbiased random letters across custom subsets (such as vowels only, consonants only, uppercase, or lowercase), we utilize browser-native CSPRNG entropy with rejection sampling:
export type CaseMode = "upper" | "lower" | "mixed";
export type FilterMode = "all" | "vowels" | "consonants";
const UPPER_VOWELS = ["A", "E", "I", "O", "U"];
const UPPER_CONSONANTS = [
"B", "C", "D", "F", "G", "H", "J", "K", "L", "M",
"N", "P", "Q", "R", "S", "T", "V", "W", "X", "Y", "Z"
];
const UPPER_ALL = [...UPPER_VOWELS, ...UPPER_CONSONANTS].sort();
/**
* Procedurally generates random letters based on filtering, casing, and delimiter.
*/
export function generateRandomLetters(options: {
count: number;
filter: FilterMode;
casing: CaseMode;
unique: boolean;
delimiter: "none" | "space" | "comma" | "newline";
}): string {
const { count, filter, casing, unique, delimiter } = options;
let sourcePool: string[];
if (filter === "vowels") {
sourcePool = [...UPPER_VOWELS];
} else if (filter === "consonants") {
sourcePool = [...UPPER_CONSONANTS];
} else {
sourcePool = [...UPPER_ALL];
}
if (unique && count > sourcePool.length) {
throw new Error(`Cannot pick ${count} unique letters from a pool of ${sourcePool.length}.`);
}
const selected: string[] = [];
const pool = [...sourcePool];
const maxUint32 = 0x100000000;
const buffer = new Uint32Array(1);
for (let i = 0; i < count; i++) {
const range = unique ? pool.length : sourcePool.length;
const limit = Math.floor(maxUint32 / range) * range;
do {
window.crypto.getRandomValues(buffer);
} while (buffer[0] >= limit);
const targetIdx = buffer[0] % range;
if (unique) {
selected.push(pool.splice(targetIdx, 1)[0]);
} else {
selected.push(sourcePool[targetIdx]);
}
}
// Format case
const formatted = selected.map((char) => {
if (casing === "lower") return char.toLowerCase();
if (casing === "upper") return char.toUpperCase();
// Mixed: flip a 50/50 bit
window.crypto.getRandomValues(buffer);
return buffer[0] % 2 === 0 ? char.toUpperCase() : char.toLowerCase();
});
// Apply delimiter
const sepMap = {
none: "",
space: " ",
comma: ", ",
newline: "\n"
};
return formatted.join(sepMap[delimiter]);
}
4. Key Use Cases for Random Letter Generation
Developers and educators utilize procedural letter generation in diverse workflows:
- Classroom and Educational Phonics Drills: Elementary school teachers and language instructors use random letter sequences to conduct speed-reading drills, vowel recognition flashcards, and spelling challenge quizzes.
- Word Games & Puzzle Generation: Developers building digital anagram, crossword, and boggle-style word games require flexible character generation with controllable vowel-to-consonant ratios to guarantee solvable board configurations.
- Synthetic License and Serial Number Formatting:
When generating dummy software serial keys (e.g.,
ABCD-EFGH-JKLM-NPQR), pairing random uppercase consonants with numbers from the Random Number Generator produces high-entropy keys that avoid ambiguous characters like0vsOor1vsI. - Genetic Algorithm Mocking:
Bioinformatics developers test sequence alignment algorithms (like BLAST) using mock DNA nucleotide strings generated strictly from the 4-letter alphabet
{A, C, G, T}.
5. Privacy and Client-Side Execution
When generating random characters for security tokens or internal testing, transmitting generation parameters across the public internet introduces unnecessary risk.
By using the Random Letter Generator on Casetivo, all character computations execute locally inside your browser's sandboxed JavaScript engine. No keystrokes, seed values, or outputs ever leave your machine, guaranteeing private, instantaneous results.