cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Recursion

Learn how JavaScript functions call themselves, why every recursive function needs a base case, how the call stack grows and unwinds, and when recursion is clearer than a loop.

By the end you can
  • 01
    Read a recursive functionSeparate the base case from the recursive case and predict simple results like factorial.
  • 02
    Explain the stackWatch calls push onto the call stack and pop off when they return.
  • 03
    Choose recursion or a loopUse recursion for naturally nested data, and know when iteration is safer.

Functions that call themselves

Recursion means a function calls itself to solve a smaller version of the same problem. The idea sounds circular at first, but you already know the shape from everyday life: solve a big thing by opening a smaller thing inside it, then a smaller one, until you reach the smallest piece that needs no more opening.

A recursive function always has two jobs. First, it needs a direct answer for the smallest input. That is the base case. Second, it needs a way to move every larger input toward that smallest input. That is the recursive case. If either part is missing, the function either stops too soon or never stops.

The short version

Recursion is a function saying: “If this is the simple case, answer now. Otherwise, call myself with a smaller case and use that answer.”

Real-life analogyRecursion is a set of nesting dolls

Open a nesting doll and you find a smaller doll with the same shape. Open that one and you find another. Eventually you reach the tiny doll that does not open. That last doll is what makes the whole game finite: without it, you would keep opening forever.

In real life: The biggest doll
In JavaScript: The first function call, like factorial(4)
In real life: A smaller doll inside
In JavaScript: The recursive call, like factorial(3)
In real life: The smallest doll that does not open
In JavaScript: The base case, like factorial(1)
In real life: Closing the dolls back up
In JavaScript: Returning from each call and combining the results

Where the analogy stops: Real dolls wait quietly. Function calls use memory on the call stack while they wait, so a program can run out of stack space if it opens too many dolls.

This lesson is still beginner-level. We will use tiny numbers and a small folder tree so the stack stays visible. Later, the deeper lessons The call stack, Stack limits & stack overflow, Tail calls, and Advanced recursion revisit the same ideas with engine-level detail.

Base & recursive cases

STEP THROUGH

The classic first recursive example is factorial. In math, 4! means 4 × 3 × 2 × 1. Written as a function, factorial(4) can ask for factorial(3), then multiply that answer by 4.

Read the two important lines before stepping:

  • if (n === 1) return 1; is the base case. It answers immediately and makes recursion stop.
  • return n * factorial(n - 1); is the recursive case. It calls the same function with a smaller number, then multiplies when the smaller answer comes back.
Step through recursive factorial
Step 0 of 18Ready
Your turn: follow the blue line

Predict the answer, then step. Watch the stack grow until the base case, then shrink as each return multiplies back up.

Running in
  1. script
Next: line 8
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
function factorial(n) {  if (n === 1) {    return 1;  }  return n * factorial(n - 1);} console.log(answer);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Choose the starting number on line 8.

Changing n starts a fresh replay. Try 1, then 5, and compare how tall the stack gets.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Notice the order. JavaScript cannot finish 4 * factorial(3) until factorial(3) returns. That call cannot finish until factorial(2) returns, and so on. The base case is not a decoration; it is the first answer the waiting calls can build on.

Base case or recursive case?
  • if (n === 1) return 1;
  • return n * factorial(n - 1);
  • if (!node.children) return 1;
  • total = total + countFiles(child);
  • if (items.length === 0) return 0;
  • return sum(rest) + first;
Try it yourself
0 of 6 correct

Sort each line by its job. Base cases answer directly; recursive cases call the same function again.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

The call stack: plates that wait

INTERACTIVE

Every function call needs a place to remember its parameters, local variables, and where to continue after the call returns. JavaScript tracks those waiting calls with the call stack. When a function is called, a stack frame is pushed on top. When it returns, that frame pops off and the caller underneath continues.

Real-life analogyThe call stack is a stack of plates

Imagine washing plates and stacking them. You can only add to the top and take from the top. Recursive calls work the same way. The newest call is on top, and the older calls wait underneath.

In real life: Put a plate on top
In JavaScript: Call a function and push a stack frame
In real life: Only the top plate is active
In JavaScript: The most recent call runs now
In real life: Take the top plate off
In JavaScript: Return from that function
In real life: The plate underneath is revealed
In JavaScript: The caller continues where it paused

Where the analogy stops: Real plates can be removed from the middle if you are careful. The call stack is stricter: JavaScript always returns from the top call first.

This visualizer records a real fib(4) run. Fibonacci is intentionally repetitive: fib(4) asks for fib(3) and fib(2), and those calls ask for smaller calls again. That repetition is why later lessons introduce memoization, a way to remember answers instead of recomputing them.

Call stack plates for fib(4)
Recursive FibonacciPop out in the code editor (opens in a new tab)JavaScript
function fib(n) {  if (n <= 1) return n;  return fib(n - 1) + fib(n - 2);} const answer = fib(4);
Stack plates1 waiting call
fib(4)

Calls made so far: 1

Step 1 of 18push fib(4)

fib(4) was called, so a new plate goes on top. fib(4) makes 9 total calls because fib repeats smaller questions.

A conceptual stack visualization recorded from a real fib(4) run. fib(4) returns 3; memoization later avoids the repeated calls.
This is a replay, not a debugger

The player shows recorded events from instrumented lesson code. It is honest about the call order and values, but it is not your browser’s debugger. The published Debugging in the browser lesson shows the real Call Stack panel.

Stack overflow: when recursion never reaches the smallest doll

REAL ERROR

Recursion without a reachable base case is like standing between two mirrors: the reflections keep going. In code, each reflection is another function call. The stack grows until the engine refuses to add another frame.

Real-life analogyStack overflow is plates hitting the ceiling

Face two mirrors toward each other and you see reflection after reflection. There is no “smallest reflection.” A recursive function with no base case has the same problem: it keeps asking itself again.

In real life: Two mirrors reflect forever
In JavaScript: A function calls itself without getting closer to a base case
In real life: The plate tower hits the ceiling
In JavaScript: The call stack reaches its limit
In real life: You stop stacking
In JavaScript: The engine throws an error instead of crashing the whole page

Where the analogy stops: A mirror image is harmless. Runaway recursion uses real memory and time, so the engine must stop it.

Most V8-based browsers and Node.js report this as RangeError: Maximum call stack size exceeded. Other engines can use different wording, such as “too much recursion.” Click the button to run a tiny unsafe function inside try/catch. The page catches the error and shows the approximate depth reached in your browser, which varies by engine and settings.

Safely catch runaway recursion
No base casePop out in the code editor (opens in a new tab)JavaScript
let depth = 0;function noBaseCase() {  depth = depth + 1;  return noBaseCase();} try {  noBaseCase();} catch (error) {  console.log(error.name);  console.log(depth > 0);}
Caught resultnot run
  • error name—
    approx depth—
    recoverable?click Run
Try it yourself

This deliberately missing base case runs only when you click. try/catch catches the error so the page can keep going.

A real runaway recursive function, caught immediately. V8 says RangeError: Maximum call stack size exceeded; other engines can word the message differently.

The fix is not “make the limit bigger.” The fix is to make sure every recursive step moves toward the base case, or to rewrite the algorithm as a loop when the depth can be large.

Recursion vs loops

COMPARE

Recursion and loops can solve many of the same problems. The earlier Loops: while & for lesson showed how to repeat work with one frame that changes a counter. Recursion repeats work by making new function calls. Both are useful, but they have different costs.

Same result: recursive sum and loop sum
Recursion vs loopPop out in the code editor (opens in a new tab)JavaScript
function sumToRecursive(n) {  if (n === 0) return 0;  return n + sumToRecursive(n - 1);} function sumToLoop(n) {  let total = 0;  for (let current = n; current > 0; current = current - 1) {    total = total + current;  }  return total;}
n = 5same answer
  • sumToRecursive(5)15
    sumToLoop(5)15
Try it yourself

Both versions return 15. The recursive version uses 6 calls; the loop keeps one frame and changes total.

Iteration is usually faster and cannot overflow the call stack. Recursion can be clearer when the problem is naturally nested.
Choosing recursion or iteration
QuestionRecursionLoop
What changes?Each call gets new parameters and waits for smaller callsOne frame updates variables each trip through the loop
Best fitNested or self-similar data: trees, folders, menusFlat repetition: count, scan a list, retry a fixed number of times
RiskToo many calls can overflow the stackInfinite loops can freeze work if the condition never changes
PerformanceUsually more overhead because calls allocate framesUsually faster and stack-safe
Tail callsDo not rely on optimization in everyday JavaScript; Safari’s JavaScriptCore implements proper tail calls, most engines do notNo tail-call optimization needed

A good beginner rule: if the data is flat, try a loop first. If the data contains smaller versions of itself, recursion may read like the problem statement. The later Tail calls lesson covers why portable JavaScript should not rely on tail-call optimization.

Recursion or loop?
  • Walk every file in a folder tree
  • Add the numbers 1 through 100
  • Render comments with replies inside replies
  • Print 10, 9, 8… once
  • Search a menu where each item may have children
  • Total the prices in one flat array
Try it yourself
0 of 6 correct

Choose the clearer tool for each scenario. There can be many working solutions; this sorter rewards the usual beginner-friendly choice.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Recursive data such as trees

INTERACTIVE

Recursion shines when your data is recursive too. A folder can contain files and more folders. A comment can have replies, and each reply can have replies. A menu item can have children, and each child can have children. That shape is called a tree.

Stage 3 has full lessons on objects and arrays. For this lesson, read the object literal as a tiny model: folders have a children array, and files have a size. The recursive function does not need to know how deep the tree is. It only knows how to handle one node: if it has no children, count one file; otherwise count each child and add the answers.

Walk a folder tree recursively
Counting files in a treePop out in the code editor (opens in a new tab)JavaScript
const tree = {  name: "site",  children: [    { name: "index.html", size: 4 },    {      name: "assets",      children: [        { name: "logo.svg", size: 2 },        { name: "app.js", size: 6 },      ],    },    { name: "notes.txt", size: 1 },  ],}; function countFiles(node) {  if (!node.children) return 1;  let total = 0;  for (const child of node.children) {    total = total + countFiles(child);  }  return total;}
Current nodesite
  • evententer
    depth0
    running total0
Step 1 of 17enter: site

Entering site. If it has children, the function will call itself for each one.

Objects and arrays get their own lessons in Stage 3. For now, read this tree as folders with children and files with sizes.

That is the real strength of recursion: the code for “one node” also works for the root, for a folder inside the root, and for a folder several levels down. Each call focuses on its own node and trusts the same function to solve the children.

Where you’ll use this

You do not need recursion for every beginner task, but you will meet it in real front-end work whenever data nests. Common examples include:

  • Rendering a navigation menu whose items can contain submenus.
  • Counting or searching files in a project tree.
  • Walking nested comments or replies in a discussion interface.
  • Flattening nested arrays after a data import.
  • Splitting a problem in half, such as binary search or divide-and- conquer sorting, when you learn algorithms later.
Professional habit

Put the base case near the top, use a smaller input in the recursive case, and test the smallest input first. That one habit prevents a surprising number of recursion bugs.

Common misconceptions

“A recursive function runs all copies at the same time.”

Only the top stack frame is actively running. Older calls are paused, waiting for the call above them to return.

“The base case must always be 0.”

The base case is whatever input has a direct answer. Factorial in this lesson uses 1; summing down often uses 0; a file node uses “no children.”

“Recursion is always more advanced and better.”

Recursion is a tool, not a badge. For flat repetition, a loop is usually easier to read, faster, and stack-safe.

“If I add a base case anywhere, I’m safe.”

The recursive case must actually move toward that base case. If countDown(n) calls countDown(n + 1), a base case at 0 will never be reached from positive numbers.

“JavaScript will optimize tail recursion for me.”

Do not rely on that. Proper tail calls exist in the standard and Safari’s JavaScriptCore implements them, but most JavaScript engines you meet in everyday development do not remove those stack frames.

Practice recursion

5 EXERCISES

Trace the values before checking the answer. For the writing tasks, paste the solution into your browser console or editor and add your own extra tests.

Exercise 1 · Warm-upPredict factorial(5)

Predict the output before running the code.

Starter codePop out in the code editor (opens in a new tab)JavaScript
function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}
console.log(factorial(5));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeWrite sumTo(n) recursively

    Write a function that returns n + (n - 1) + ... + 1. Check that sumTo(5) prints 15.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function sumTo(n) {
      // your base case and recursive case here
    }
    console.log(sumTo(5));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFind the missing base case bug

      The starter code never stops. Add a base case so countDown(3) eventually prints done.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      function countDown(n) {
        return countDown(n - 1);
      }
      console.log(countDown(3));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengeCount files in a nested tree

        Finish countFiles for the tree from the lesson. It should count files, not folders.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const tree = {
          name: "site",
          children: [
            { name: "index.html", size: 4 },
            { name: "assets", children: [
              { name: "logo.svg", size: 2 },
              { name: "app.js", size: 6 },
            ] },
            { name: "notes.txt", size: 1 },
          ],
        };
        
        function countFiles(node) {
          // return 1 for a file; otherwise count every child
        }
        console.log(countFiles(tree));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeConvert recursive countdown to a loop

          Rewrite a recursive countdown as a loop that prints exactly 3, 2, 1, Go!.

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            8 QUESTIONS

            Every answer explains why it is right or wrong. If a code question feels hard, trace the base case and the recursive case on paper.

            Lesson quiz · 8 questionsScore: first tries count
            1. Question 1 of 8What is recursion?

              Choose an answer to see the explanation.

            2. Question 2 of 8What does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function factorial(n) {
                if (n === 1) return 1;
                return n * factorial(n - 1);
              }
              console.log(factorial(4));

              Choose an answer to see the explanation.

            3. Question 3 of 8Which line is the base case?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function sumTo(n) {
                if (n === 0) return 0;
                return n + sumTo(n - 1);
              }
              console.log(sumTo(3));

              Choose an answer to see the explanation.

            4. Question 4 of 8What happens to the call stack while factorial(4) is looking for factorial(1)?

              Choose an answer to see the explanation.

            5. Question 5 of 8What error do most browsers show for runaway recursion?

              Choose an answer to see the explanation.

            6. Question 6 of 8Which task is usually a better fit for recursion than a loop?

              Choose an answer to see the explanation.

            7. Question 7 of 8What do the recursive and loop versions print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function sumToRecursive(n) {
                if (n === 0) return 0;
                return n + sumToRecursive(n - 1);
              }
              function sumToLoop(n) {
                let total = 0;
                for (let current = n; current > 0; current = current - 1) {
                  total = total + current;
                }
                return total;
              }
              console.log(sumToRecursive(4) + "," + sumToLoop(4));

              Choose an answer to see the explanation.

            8. Question 8 of 8JavaScript engines generally do not optimize ordinary tail recursion. Which wording is safest?

              Choose an answer to see the explanation.

            Key takeaways

            • Recursion means a function calls itself with a smaller version of the same problem.
            • Every recursive function needs a reachable base case and a recursive case that moves toward it.
            • The call stack grows while calls wait, then shrinks as each call returns.
            • Missing or unreachable base cases can throw RangeError when the stack limit is reached.
            • Loops are usually better for flat repetition; recursion is often clearer for trees and other nested data.

            Remember the one-liner.
            A recursive function answers the smallest case directly, then solves bigger cases by calling itself on smaller ones.

            Up next: Objects & arrays in Stage 3 (text only for now), where the tree-shaped data from this lesson gets its full explanation.

            CompleteFrontend Clear concepts. Working examples.