Recursion

A function that calls itself — and why that’s surprisingly powerful

Core CS Concept Used in Beale Cipher JavaScript

What Is Recursion?

Recursion is when a function solves a problem by calling itself with a smaller version of the same problem. Each call makes progress until the problem is small enough to solve directly — and then the chain of calls unwinds, combining all the partial answers into a final result.

“In order to understand recursion, one must first understand recursion.” — Classic CS joke (and a perfect illustration of the concept)

Real life is full of recursive thinking. A Russian nesting doll contains a smaller version of itself, which contains a smaller version of itself, until you reach the tiny solid doll at the centre. A directory on your computer can hold files and other directories — each of which can hold more files and more directories, on and on until you reach empty folders. Recursion describes both naturally.

Key idea: A recursive function is one that calls itself as part of its own definition. Every time it calls itself it works on a smaller piece of the problem, so eventually the calls stop.

The Three Rules of Recursion

Every correct recursive function has three parts. Missing any one of them causes either a wrong answer or an infinite loop that crashes your program.

🛑
1. Base Case

The condition where the function stops calling itself and returns directly — no more recursion.

🔁
2. Recursive Case

The function calls itself — but with a smaller or simpler version of the input.

📉
3. Progress

Each call must move closer to the base case. Otherwise you get infinite recursion.

Stack Overflow — the classic recursion bug! If your function never reaches the base case, it calls itself forever. The JavaScript engine keeps adding new call frames to the call stack until it runs out of memory and throws a RangeError: Maximum call stack size exceeded. Always double-check your base case.

A Simple Example — Countdown

Before diving into our project code, let’s look at the simplest possible recursion: printing a countdown from n to zero.

// Recursive countdown
function countdown(n) {
    // Rule 1 — base case: stop when we reach zero
    if (n < 0) return;

    console.log(n);

    // Rule 2 & 3 — recursive case: call with a smaller n
    countdown(n - 1);
}

countdown(3);
// Output:  3  2  1  0

Trace through it mentally. countdown(3) prints 3, then calls countdown(2). That prints 2 and calls countdown(1). That prints 1 and calls countdown(0). That prints 0 and calls countdown(-1). -1 < 0 is true → the function returns immediately. Done.

Classic: Factorial

Factorial is the textbook recursion example. 5! = 5 × 4 × 3 × 2 × 1. Notice that 5! = 5 × 4! — it’s defined in terms of itself.

function factorial(n) {
    if (n <= 1) return 1;          // base case
    return n * factorial(n - 1);  // recursive case — smaller n each time
}

console.log(factorial(5)); // 120
factorial(5) → 5 * factorial(4) ... waiting factorial(4) → 4 * factorial(3) ... waiting factorial(3) → 3 * factorial(2) ... waiting factorial(2) → 2 * factorial(1) ... waiting factorial(1) ← returns 1 (base case hit!) ← returns 2 * 1 = 2 ← returns 3 * 2 = 6 ← returns 4 * 6 = 24 ← returns 5 * 24 = 120

Recursion vs. a Loop

Any recursion can be rewritten as a loop, and any loop can be rewritten as recursion. So why choose one over the other? It comes down to what makes the solution clearest.

Loop (for / while) Recursion
Best for Counting, iterating over a flat list, simple repetition Problems that naturally break into smaller copies of themselves (trees, nested structures, staged sequences)
Readability Familiar and easy to follow for simple repetition Can mirror the problem's structure exactly; elegant once you “see” it
Risk Off-by-one errors; break/continue logic can tangle Stack overflow if base case is wrong or missing
Timing Runs synchronously — all iterations complete before the next line Can be made asynchronous using setTimeout or await, spacing calls over time
The crucial timing difference: A for loop cannot pause between iterations — it runs all the way through before anything else can happen on screen. A recursive call scheduled with setTimeout( fn, delay ) yields control back to the browser between calls, so the canvas can animate and the user can see each step as it happens. That is exactly what we needed in the Beale Cipher.

Recursion in Our Code — revealNextLetter()

In every Beale Cipher sketch (BC3, BC4, BC5, and the Generalized version), once all the digit particles have flown home, the word needs to reveal itself one letter at a time — each letter popping in with a brief scale-burst, then the next letter, then the next, until the full word is visible.

We needed timed gaps between letter reveals so the student can watch each one materialise. That ruled out a simple for loop (which would reveal all letters instantly with no visible pause). We used recursive setTimeout instead.

Here is the actual function, exactly as it appears in the sketches:

/**
 * revealNextLetter — cascaded reveal: each letter pops in sequence.
 * Spawn the next reveal after a short delay, giving each one time to pop.
 */
function revealNextLetter(startIdx) {

    // Scan from startIdx forward to find the next letter to reveal
    for (let i = startIdx; i < chars.length; i++) {
        const s = chars[i];
        if (s.origLetter === ' ') continue;  // skip spaces
        if (s.cipherNum === null) continue;  // skip unencodeable chars
        if (s.revealed) continue;            // skip already-revealed

        // ── Materialise this letter ──────────────────────────────────────
        s.alpha    = 100;
        s.revealed = true;
        s.popTimer = POP_DUR;          // starts the scale-pop animation
        s.popScale = POP_SCALE_PEAK;

        spotlightWord(s.cipherNum, 185);  // highlight word in ticker

        // ── Recursive call — scheduled 220ms in the future ───────────────
        setTimeout(() => revealNextLetter(i + 1), 220);
        return;   // ← critically: stop this call here, yield to browser
    }

    // ── Base case: no more letters to reveal ─────────────────────────────
    setTimeout(() => {
        animState = 'done';
        if (assembleBtn) assembleBtn.disabled = true;
    }, 400);
}

Identifying the Three Rules

🛑 Base Case
The for loop finishes without finding any more unrevealed letters. Execution falls through to the block that sets animState = 'done' — no further recursive call is made.
🔁 Recursive Case
setTimeout(() => revealNextLetter(i + 1), 220) — the function schedules a call to itself, passing the index of the next letter so the scan does not re-reveal letters already shown.
📉 Progress
Each call passes i + 1, so the starting index strictly increases every time. With a finite number of characters, the base case is always reached.

Why return after scheduling the next call?

Notice the bare return; immediately after the setTimeout line. This is essential. Without it, the loop would continue and reveal the next letter in the same call — skipping the 220 ms pause entirely. The return hands control back to the browser, which draws the current frame, runs any queued animations, and then — 220 ms later — fires the next recursive call.

The pattern in one sentence: Do one thing, schedule the next call, then get out of the way. This is sometimes called a recursive timeout chain and is a classic technique for building timed animation sequences without blocking the browser’s render loop.

Call-by-Call Trace

Let’s walk through a concrete example. Suppose the word is CAT (3 letters, indices 0, 1, 2). All digits have just landed home. revealNextLetter(0) is called.

— All digits home. checkAllHomed() fires —
revealNextLetter(0) i=0 → 'C' not yet revealed → materialise 'C' (scale-pop starts) setTimeout( ()=>revealNextLetter(1), 220ms ) return ← yields to browser; canvas draws the 'C' pop
— 220 ms later — revealNextLetter(1) i=1 → 'A' not yet revealed → materialise 'A' (scale-pop starts) setTimeout( ()=>revealNextLetter(2), 220ms ) return ← yields; canvas draws the 'A' pop
— 220 ms later — revealNextLetter(2) i=2 → 'T' not yet revealed → materialise 'T' (scale-pop starts) setTimeout( ()=>revealNextLetter(3), 220ms ) return
— 220 ms later — revealNextLetter(3) i=3 → loop ends, no more letters → BASE CASE HIT setTimeout( animState='done', 400ms ) — word fully revealed —

Total visible time: 3 letters × 220 ms = 660 ms of animated cascade, plus the final 400 ms settling delay. Try counting along next time you press Assemble in a Beale Cipher app!

Why Recursion? — The Rationale & Benefits

Why we chose recursion here

1
Timed gaps between reveals. A for loop runs all iterations synchronously in a single frame — the browser gets no chance to draw anything in between. Using setTimeout inside recursion yields control back to the browser each time, so every letter materialises visibly before the next one starts.
2
Clean, self-describing logic. The function says exactly what it does: “reveal the next unrevealed letter, then schedule this same process for the letter after it.” A loop-based version would need an external counter variable and a callback reference — more state, more to go wrong.
3
Naturally handles variable-length words. The word length is not known at the time the function is written. Recursion handles 3 letters and 30 letters with identical code — the base case finds the end automatically regardless of word length.
4
Skips silently over spaces and unencodeable characters. The continue statements inside the loop let the function walk past spaces (which have no cipher number) without any special-case code around the recursive call. The recursion just keeps scanning until it finds real work to do.

The broader benefits of recursion as a technique

When recursion shines
Tree / graph traversal File systems, HTML DOM, game scene graphs, family trees — all naturally recursive structures. A recursive function mirrors the shape of the data.
Divide and conquer Merge Sort, Quick Sort, and Binary Search all split the problem in half recursively. These are among the fastest sorting and searching algorithms known.
Fractals & procedural art The Chaos Theory sketch in this saga uses recursive-style thinking (each walk step building on the last). Sierpiński triangles, Koch snowflakes, L-system trees — all drawn with recursion.
Parsing & compilers Programming languages are parsed recursively. An expression can contain sub-expressions, which can contain sub-expressions — recursion describes this elegantly.
Asynchronous cascades Any time you need step A to finish before step B starts — and you want a visible gap between them — setTimeout recursion is a clean, readable pattern.
The bottom line: Recursion is not a trick or a curiosity — it is a fundamental problem-solving strategy used in operating systems, compilers, databases, graphics engines, and AI search algorithms. Learning to think recursively is one of the biggest leaps a programmer can make. Once it clicks, you will see recursive structure everywhere.

Where to Go Next

You have just seen a real-world recursive function shipping in a polished animation app. Here are the best ways to deepen your understanding:

Practice problems to try

  1. Sum of a list. Write sumList([3, 1, 4, 1, 5]) recursively — the sum of a list is the first element plus the sum of the rest.
  2. Reverse a string. Write reverseStr("hello") recursively — the reverse of a string is its last character followed by the reverse of everything before it.
  3. Flatten nested arrays. Write flatten([1, [2, [3, 4]], 5]) that returns [1, 2, 3, 4, 5]. Each element is either a number (base case) or an array (recurse into it).
  4. Modify our code. Open Beale Cipher (Generalized) and find revealNextLetter in swGenBealeCipher1Sketch.js. Change the delay from 220 to 600 and watch the cascade slow down dramatically. Then try 0 — what happens, and why?

Related pages in this project

  • Assemble Algorithm — a deep-dive into the digit-particle homing physics that runs just before revealNextLetter is called.
  • Destination: Recursion — a broader retrospective on recursion patterns across the entire SWIsland Saga, with pros, cons, and stage-by-stage examples.

External resources

See it in action

Open a Beale Cipher app and press Assemble. Every letter that pops in — right on cue, one after another — is powered by the recursive function you just studied.

Open Beale Cipher → Back to Saga Index