← thecodex.expert · The Codex Family of Knowledge
Tier 1 · Beginner · TypeScript Project

Password Generator

Generate strong, random passwords of any length. Learn secure randomness and building strings from a pool of characters.

🧠 Teaches how to think spoonfed, every age Last verified:

1 The Problem

We want a tool that creates a strong password: a random mix of letters, numbers, and symbols, of a length the user chooses. It teaches building a string from random choices — and an important lesson about which randomness is safe for security.

Where this shows up: password managers, generating API keys, session tokens, temporary access codes, unique IDs. Any time software needs something unpredictable that an attacker cannot guess.

2 How to Think About It

Think about how a strong password is built, before any code:

The plan — in plain English
1. Build a pool of allowed characters (letters + digits + symbols). → 2. Ask how long the password should be. → 3. Pick that many random characters from the pool. → 4. Join them into one string and show it. The key detail: use cryptographically secure randomness, not ordinary randomness.

Build character pool

Ask for length

Pick that many random chars

Join into a password

Show the password

3 The Build — explained part by part

Here is the complete generator. Read each part’s note below — you should understand the whole thing from the notes alone.

TypeScriptpassword.ts
import * as readline from "node:readline";
import { stdin, stdout } from "node:process";
import { randomInt } from "node:crypto";

// Build the pool of characters a password can use.
const LOWER = "abcdefghijklmnopqrstuvwxyz";
const UPPER = LOWER.toUpperCase();
const DIGITS = "0123456789";
const SYMBOLS = "!@#$%^&*";
export const POOL = LOWER + UPPER + DIGITS + SYMBOLS;

// generate picks `length` random characters from the pool, securely —
// randomInt comes from Node's crypto module, not Math.random().
export function generate(length: number): string {
  let password = "";
  for (let i = 0; i < length; i++) {
    password += POOL[randomInt(0, POOL.length)];
  }
  return password;
}

async function main(): Promise<void> {
  const rl = readline.createInterface({ input: stdin, terminal: false });
  const it = rl[Symbol.asyncIterator]();
  stdout.write("Password length: ");
  const answer = (await it.next()).value ?? "";
  const length = Number(answer.trim());
  rl.close();

  const password = generate(length);
  console.log(`Your password: ${password}`);
}

if (require.main === module) {
  main();
}
⚠ No in-browser playground here
Running real, type-checked TypeScript in the browser needs either a full copy of the compiler or a third-party CDN script — the same kind of external dependency this site avoids relying on for a core teaching example. Copy the code below and run it with Node on your own machine instead; the “Run It” section explains exactly how.
What each part does — in plain words
const POOL = LOWER + UPPER + DIGITS + SYMBOLS — building the pool from named pieces (same idea as Python’s string.ascii_letters) makes it obvious exactly which characters a password can contain.

import { randomInt } from "node:crypto" — the same secure generator used by the number-guessing game, but here it matters for real: Math.random() is not cryptographically secure and must never be used to generate anything resembling a password or a secret.

POOL[randomInt(0, POOL.length)] — pick one random index into the pool string, repeated length times. randomInt(0, n) returns a whole number from 0 up to (but not including) n — exactly the valid index range for a string of length n.

export function generate(length: number): string — pulling the loop into its own typed function (rather than writing it inline in main) is what makes it directly testable, with no prompt or typed input in the way.
Common mistakes — and how to avoid them
✗ Using Math.random() to pick password characters.
✓ Math.random() is not designed to be unpredictable to an attacker. Always use crypto.randomInt (or crypto.randomBytes) for anything security-sensitive.
✗ Writing Math.floor(Math.random() * POOL.length) as a “good enough” substitute for randomInt.
✓ Besides the security problem above, floating-point rounding can introduce a very slight bias toward some indexes. randomInt avoids both problems.
✗ Asking for a negative or zero length and getting a confusing empty string back with no explanation.
✓ A real version would validate length > 0 before generating — see “Try this next” below.

4 Test & Prove Each Part

How do we know this works? We pull the real logic into small, plain functions and check each one against cases we already know the answer to.

A length-12 request gives 12 characters
Every character comes from the allowed pool
Two passwords are (almost) never identical
TypeScriptpassword.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { generate, POOL } from "./password-generator";

test("a length-12 request gives 12 characters", () => {
  assert.equal(generate(12).length, 12);
});

test("every character comes from the allowed pool", () => {
  for (const ch of generate(50)) {
    assert.ok(POOL.includes(ch));
  }
});

test("two passwords are (almost) never identical", () => {
  assert.notEqual(generate(16), generate(16));
});

Compile with npx tsc then run node --test password.test.js. Testing randomness directly is impossible, so instead we test its guarantees: the right length, only pool characters, and (practically) no repeats.

5 The Interface

INPUTlengtha whole number
What it expects
Password length: 16
OUTPUTpasswordrandom secure string
What it returns
Your password: Rxp7QarCT*DvpdFA

6 Run It & Automate It

Save the code as password.ts, compile with npx tsc, and run with node password.js — or run it directly with npx tsx password.ts.

Run it locally
npx tsc password.ts && node password.js
Type a length and get back a secure random password of that length.

A CI tool like Jenkins runs the type-checker and tests automatically whenever the code changes — every line below has a plain explanation.

What you should see when it works
Terminala real run
Password length: 16
Your password: JK5O&aFMvc3s3GWC
If it breaks — how to fix it
🚨 Every password looks suspiciously similar in character mix
Check that POOL really does combine letters, digits, and symbols — a typo that drops one of the four building blocks silently shrinks the pool.
🚨 RangeError: The value of "max" is out of range
randomInt requires its upper bound to be a positive integer greater than the lower bound; this fires if POOL is accidentally empty.
GroovyJenkinsfile
// Jenkinsfile &mdash; runs the type-checker and tests automatically every time the code changes.
pipeline {
    agent any                                  // run on any available machine

    stages {
        stage('Get the code') {
            steps { checkout scm }             // download the latest code
        }
        stage('Set up Node') {
            steps {
                sh 'node --version'                              // confirm Node is installed
                sh 'npm install -D typescript @types/node'       // zero runtime deps &mdash; just the compiler and its Node types
            }
        }
        stage('Type-check and test') {
            steps {
                sh 'npx tsc --noEmit'                 // catch type errors before anything runs
                sh 'npx tsc'                           // compile to plain JavaScript
                sh 'node --test password.test.js'            // Node's built-in test runner, no extra install needed
            }
        }
    }

    post {
        success { echo 'All tests passed.' }
        failure { echo 'A test failed &mdash; look above.' }
    }
}
🎯 Try this next — make it yours

You have a working password generator. Extend it:

  1. Reject a non-positive length. Print a clear error instead of silently returning an empty string. (Teaches: input validation.)
  2. Let the user opt out of symbols. Some sites do not accept them. (Teaches: building the pool conditionally.)
  3. Guarantee variety. Force at least one digit and one symbol to appear. (Teaches: combining a guarantee with randomness.)
  4. Estimate strength. Print how many possible passwords exist for that pool and length (pool.length ** length). (Teaches: BigInt for numbers too large for number.)
What you learned
You learned why crypto.randomInt (not Math.random) is the right tool whenever randomness has to be unpredictable, plus how to pull a generator into a small, directly-testable, typed function. Related reference: Basic Types, The TypeScript Compiler.