completefrontend
Open lab My progress
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 functionFind the base case and the recursive case. Predict simple results, like factorial(4).
  • 02
    Explain the call stackWatch each call go on top of the call stack, and come off again when it returns.
  • 03
    Choose recursion or a loopUse recursion for data inside data, like folders. Know when a loop is safer.

Functions that call themselves

Recursion means a function calls itself. It does this to solve a smaller version of the same problem.

That can sound like going round in circles. It isn't, because each call gets a smaller job. In the end, the job is so small that the function can answer it straight away.

A recursive function always has two parts:

  • The base case is the smallest input. The function answers it directly, without calling itself.
  • The recursive case is every bigger input. The function calls itself with a smaller input, then uses that answer. That call is a recursive call.

You need both parts. Without a base case, the function never stops calling itself. Without a recursive case, it can only answer the smallest input.

The short version

A recursive function says: "If this is the smallest case, I answer now. If not, I call myself with a smaller case and use that answer."

Real-life analogyFolders inside Downloads

You open your Downloads folder to find a file. Inside is another folder, so you open it. Inside that is one more folder, so you open that too. At last you find a file. A file is the end: there is nothing more to open.

A recursive function works the same way. Each call opens a smaller version of the same job. It stops at a job with nothing more to open.

In real life: The Downloads folder
In JavaScript: The first call, like countDown(3)
In real life: A folder inside it
In JavaScript: The call it makes, like countDown(2)
In real life: A file, with nothing inside to open
In JavaScript: The base case, like countDown(0)
In real life: Going back out to the folder you came from
In JavaScript: Each call returning its answer to the call that made it

This lesson stays at beginner level. It uses small numbers and a small folder tree, so you can see every call. Later lessons go deeper into the same ideas: The call stack, Stack limits & stack overflow, Tail calls, and Advanced recursion.

Base & recursive cases

STEP THROUGH

Start with a countdown. countDown(n) counts from n down to 0. When it reaches 0, it answers "Go!".

A countdown that stops at 0Pop out in the code editor (opens in a new tab)JavaScript
function countDown(n) {
  if (n === 0) {
    return "Go!";
  }
  return countDown(n - 1);
}

console.log(countDown(3));
console.log(countDown(0));

Line by line:

  • Lines 2 to 4 are the base case. When n is 0, the function returns "Go!". It makes no new call.
  • Line 5 is the recursive case. For any other number, the function calls itself with n - 1, a number one smaller. It returns whatever that call returns.
  • Line 8 starts at 3. The calls go 3, 2, 1, then 0. At 0, the base case answers "Go!".
  • Line 9 starts at 0, so the base case answers straight away.

Both lines print Go!. Line 9 shows a good habit: test the base case on its own first.

Four calls going down like stairs: countDown(3) calls countDown(2), which calls countDown(1), which calls countDown(0). Each of the first three says: not 0, so call again. countDown(0) is the base case. It answers Go!countDown(3)countDown(2)countDown(1)countDown(0)not 0, so call againnot 0, so call againnot 0, so call againGo!base caseanswer
Each call checks its number. If the number is not 0, it calls countDown again with a number one smaller. countDown(0) is the base case: it answers "Go!" without another call. That answer is then passed back to every waiting call.

Next is factorial. In math, 4! (say "4 factorial") means 4 × 3 × 2 × 1, which is 24. As a function, factorial(4) asks for factorial(3), then multiplies that answer by 4.

Look at the two key parts before you step through the code:

  • Lines 2 to 4 are the base case. When n is 1, the function returns 1 right away. The calls stop here.
  • Line 5, return n * factorial(n - 1);, is the recursive case. It calls the same function with a number one smaller. When that answer comes back, it multiplies it by n.
Step through factorial
Step 0 of 18Ready
Your turn: follow the blue line

Guess the answer, then step. Watch the stack grow until the base case. Then watch it shrink as each call returns and multiplies.

Running in
  1. script
Next: line 8
function factorial(n) {  if (n === 1) {    return 1;  }  return n * factorial(n - 1);} console.log(answer);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Choose the starting number on line 8.

Changing n starts the replay again. Try 1, then 5. 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 can't finish 4 * factorial(3) until factorial(3) returns. And factorial(3) can't finish until factorial(2) returns, and so on. The base case gives the first finished answer. Every waiting call builds on it.

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. A base case answers directly. A recursive case calls 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 to remember a few things:

  • its parameters, like n;
  • its own variables;
  • where to carry on when a call it made comes back.

JavaScript keeps this in a list called the call stack. Each call gets one entry on it, called a stack frame.

  • A new call puts its frame on top of the stack.
  • When that call returns, its frame comes off the top.
  • Then the call underneath carries on from where it stopped.
Real-life analogyThe call stack is a stack of plates

Picture a pile of clean plates. You can only put a plate on the top. You can only take a plate from the top. Calls work the same way. The newest call is on top. Older calls wait underneath.

In real life: Put a plate on top
In JavaScript: Call a function: its frame goes on top
In real life: Only the top plate is in use
In JavaScript: The newest call is the one running
In real life: Take the top plate off
In JavaScript: That function returns
In real life: The plate underneath is on top again
In JavaScript: The call underneath carries on from where it stopped
A pile of four calls. factorial(4) is at the bottom, then factorial(3), factorial(2), and factorial(1) on top. The top call returns 1. Each call below waits, then multiplies: 2 times 1 is 2, 3 times 2 is 6, 4 times 6 is 24.newestfirstfactorial(1)returns 1factorial(2)waits, then 2 × 1 = 2factorial(3)waits, then 3 × 2 = 6factorial(4)waits, then 4 × 6 = 24
The call stack while factorial(4) runs. The first call is at the bottom. Each new call goes on top. Only the top call runs. It finishes first, and then the call under it can use its answer.
Code after the call runs on the way backPop out in the code editor (opens in a new tab)JavaScript
function buildNumber(n) {
  if (n === 0) return "";
  return buildNumber(n - 1) + n;
}

console.log(buildNumber(3));

buildNumber(3) calls buildNumber(2) and waits. That calls buildNumber(1) and waits. That calls buildNumber(0), the base case. It returns an empty string, "".

Now the waiting calls finish, newest first. Adding a number to a string joins them as text:

  • buildNumber(1) adds 1 to the empty string and returns "1".
  • buildNumber(2) adds 2 and returns "12".
  • buildNumber(3) adds 3 and returns "123".

So the output is 123. Code written after a recursive call runs on the way back, once the smaller call has returned.

The next player records a real run of fib(4). fib finds Fibonacci numbers. In that list of numbers, each one is the sum of the two before it: 0, 1, 1, 2, 3, 5, and so on. fib(n) gives the number at position n, counting from 0. So fib(4) is 3.

Line 3 calls fib twice. So fib(4) asks for fib(3) and fib(2), and each of those asks for smaller ones again. Some of the same questions get asked more than once. A later lesson shows memoization: saving each answer so you don't work it out again.

The call stack for fib(4), as a pile of plates
Fibonacci with recursionPop 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 call on the stack
fib(4)

Calls made so far: 1

Step 1 of 18call fib(4)

fib(4) was called, so a new plate goes on top. In total, fib(4) makes 9 calls, because fib asks some of the same smaller questions more than once.

A picture of the call stack, recorded from a real fib(4) run. fib(4) returns 3. A later lesson shows memoization, which saves answers so the repeated calls aren't needed.
This is a replay, not a debugger

The player shows calls that were recorded from this lesson's own code. The order and the values are real. But it is not your browser's debugger. The Debugging in the browser lesson shows the real Call Stack panel.

Stack overflow: when recursion never stops

REAL ERROR

What if a function calls itself and never reaches a base case? Each call adds one more frame to the call stack, so the stack keeps growing. It can't grow forever. At some point JavaScript stops and throws an error. This is called a stack overflow.

Real-life analogyA phone alarm that never gets turned off

An alarm keeps ringing until someone turns it off. A recursive function also needs something that stops it: a base case it can reach. Without one, it keeps calling itself.

In real life: The alarm rings again
In JavaScript: A function calls itself again
In real life: Turning it off
In JavaScript: Reaching the base case
In real life: It never gets turned off
In JavaScript: The stack keeps growing until JavaScript stops it
A pile of noBaseCase() calls that keeps growing, because there is no base case. An arrow leads to a console window that shows RangeError: Maximum call stack size exceeded.keeps growingnoBaseCase()noBaseCase()noBaseCase()noBaseCase()no base caseconsoleRangeError:Maximum call stacksize exceeded
Nothing tells noBaseCase() to stop, so each call adds one more call on top. The pile can't grow forever. When it gets too tall, JavaScript stops and throws a RangeError.

Chrome, Edge, and Node.js show this error as RangeError: Maximum call stack size exceeded. They all use V8, the engine that runs JavaScript inside them. Other browsers can word it differently. Firefox, for example, says "too much recursion".

Click the button below to run a tiny function with no base case. It runs inside try/catch:

  • try runs some code.
  • catch catches the error if one is thrown, so the page keeps working.

The result shows about how many calls your browser made before it stopped. That number depends on the browser and its settings.

Catch a function that never stops
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);}
What was caughtnot run yet
  • error name—
    calls made, about—
    page still works?click Run
Try it yourself

This function has no base case on purpose. It only runs when you click. try/catch catches the error, so the page keeps working.

A real function that never stops calling itself, caught straight away. Chrome, Edge, and Node.js say RangeError: Maximum call stack size exceeded. Other browsers can word it differently.

The fix is not a bigger limit. Instead:

  • make sure every recursive call moves closer to the base case, or
  • use a loop when a very large number of calls could be waiting at once.

Recursion vs loops

COMPARE

Recursion and loops can solve many of the same problems. You met loops in the Loops: while & for lesson. Here is how the two differ:

  • A loop repeats work inside one function call. It changes a counter each time round.
  • Recursion repeats work by making a new function call each time.

Both work, but they cost different amounts. The playground below gets the same sum both ways.

Same answer: a sum with recursion and with a loop
The same sum, two waysPop 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 = 5both answers
  • sumToRecursive(5)15
    sumToLoop(5)15
Try it yourself

Both versions return 15. The recursive version makes 6 calls. The loop uses one call and keeps changing total.

A loop is usually faster, and it can't overflow the call stack. Recursion can be clearer when the data has data inside it.
Add up prices with recursionPop out in the code editor (opens in a new tab)JavaScript
function totalPrices(prices) {
  if (prices.length === 0) return 0;
  const [price, ...rest] = prices;
  return price + totalPrices(rest);
}

console.log(totalPrices([2, 3, 4]));
console.log(totalPrices([]));

Line by line:

  • Line 2 is the base case. An empty list adds up to 0.
  • Line 3 takes the list apart. This is called destructuring. price gets the first item. ...rest gets all the other items, as a new list.
  • Line 4 adds the first price to the total of the rest. That total comes from calling totalPrices again with the shorter list.

It prints 9 for three prices and 0 for an empty list. It works. But this is a flat list: it has no lists inside it. For a flat list, a loop or the array method reduce is usually easier to read.

The table compares the two tools. Read each row as a question, then the answer for recursion and the answer for a loop.

Recursion or a loop: how they compare
QuestionWith recursionWith a loop
What changes each time?Each call gets new parameters and waits for a smaller call.One call updates its variables each time round the loop.
What does it fit best?Data with smaller copies of itself inside, like trees, folders, and menus.Simple repeats, like counting, going through one list, or trying a few times.
What can go wrong?Too many waiting calls can overflow the stack.If the condition never changes, the loop runs forever and the page freezes.
Which is faster?Usually slower, because every call needs a new stack frame.Usually faster, and it can’t overflow the stack.
Can the engine save stack space?Don’t count on it. Safari can reuse the frame for a tail call, a call that is the last thing a function does. Most engines can’t.It doesn’t need to. A loop uses one frame.

A good rule for now: if the data is flat, try a loop first. If the data has smaller copies of itself inside, recursion can read just like the problem. The Tail calls lesson explains why you shouldn't count on engines saving stack space.

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

Pick the clearer tool for each task. Both tools can solve every task. The sorter marks the choice most beginners find easier.

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 works best when the data has the same shape inside itself. For example:

  • A folder can hold files and more folders.
  • A comment can have replies, and each reply can have replies.
  • A menu item can have sub-items, and each sub-item can have its own.

Data shaped like this is called a tree. Each item in the tree is called a node.

The examples below use objects and arrays. Stage 3 has full lessons on both. For now, read them like this:

  • An object, written in { }, holds named values, like text and replies.
  • An array, written in [ ], is a list. .length is how many items it has.
Count replies inside repliesPop out in the code editor (opens in a new tab)JavaScript
const comment = {
  text: "Useful tip",
  replies: [
    { text: "Thanks", replies: [] },
    { text: "Me too", replies: [{ text: "Same", replies: [] }] },
  ],
};

function countReplies(item) {
  if (item.replies.length === 0) return 0;
  let total = item.replies.length;
  for (const reply of item.replies) {
    total = total + countReplies(reply);
  }
  return total;
}

console.log(countReplies(comment));

Line by line:

  • Lines 1 to 7 make a comment with two replies. The second reply has one reply of its own.
  • Line 10 is the base case. An item with no replies returns 0.
  • Line 11 starts total at the number of direct replies.
  • Lines 12 to 14 visit each reply with a for...of loop. For each reply, the function calls itself and adds that reply's count.
  • Line 15 returns the total.

The example prints 3: two direct replies, plus one reply inside the second one.

The player below walks through a folder tree in the same way. In its code, a folder has a children list. A file has a size and no children. The function countFiles only needs to handle one node:

  • A file has no children, so it counts as 1. This is the base case. !node.children is true when there is no children list.
  • A folder asks countFiles about each child and adds up the answers. This is the recursive case.
A folder tree. The site folder holds index.html, the assets folder, and notes.txt. The assets folder holds logo.svg and app.js. Each file counts as 1 file. assets adds its two files to get 2 files. site adds 1, 2, and 1 to get 4 files.site/4 filesindex.html1 fileassets/2 filesnotes.txt1 filelogo.svg1 fileapp.js1 file
The same question, "how many files?", at every level. A file answers 1 straight away: that is the base case. A folder asks each child the same question and adds up the answers.
Step through counting files in a folder tree
Step 0 of 20Ready
Your turn: follow the blue line

Step through a real countFiles run. The calls and returns were recorded from the lesson's own function. This is a replay, not a browser debugger.

Running in
  1. script
Next: line 25
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;} console.log(answer);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
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.

This is the real strength of recursion. The function doesn't need to know how deep the tree goes. The same code works for the top folder, for a folder inside it, and for a folder many levels down. Each call handles its own node and lets the same function handle the children.

Where you'll use this

You won't need recursion for most beginner tasks. But you will meet it in real front-end work whenever data sits inside data. For example:

  • Showing a navigation menu where items can have sub-menus.
  • Counting or searching the files in a project's folders.
  • Showing comments with replies inside replies.
  • Flattening a nested array: turning lists inside lists into one list.
  • Splitting a problem in half again and again, as in binary search. You'll meet this when you learn algorithms later.
Professional habit

Put the base case at the top. Make every recursive call use a smaller input. Test the smallest input first. These three habits prevent most recursion bugs.

Common misconceptions

"A recursive function runs all its calls at the same time."

Only the call on top of the stack runs. The older calls are paused. Each one waits for the call above it to return.

"The base case must always be 0."

The base case is whatever input you can answer directly. In this lesson, factorial stops at 1, countDown stops at 0, and a file in the tree stops because it has no children.

"Recursion is always more advanced and better."

Recursion is a tool, not a sign of skill. For simple repeats, a loop is usually easier to read and faster. It also can't overflow the stack.

"If I add a base case anywhere, I'm safe."

Each recursive call must also move closer to that base case. If countDown(n) calls countDown(n + 1), the number keeps going up. Starting from 3, it never reaches a base case at 0.

"JavaScript will save stack space on tail calls for me."

Don't count on that. A tail call is a call that is the very last thing a function does. The JavaScript standard says engines should reuse the stack frame for it, and Safari does. Most other engines don't, so the frames still pile up.

Practice recursion

5 EXERCISES

Work out the values on paper before you check the answer. For the writing tasks, paste your code into the browser console or an editor. Then add a few tests of your own.

Exercise 1 · Warm-upPredict factorial(5)

Predict the output before you run 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) with recursion

    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 · PracticeFix the missing base case

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

      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 folder tree

        Finish countFiles for the folder tree from this lesson. It should count files only, 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 · ChallengeTurn the countdown into a loop

          Write the countdown as a loop instead of recursion. It should print 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, find the base case first. Then write out the recursive case one step at a time 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 on its way down to factorial(1)?

              Choose an answer to see the explanation.

            5. Question 5 of 8A function calls itself and never stops. What error do most browsers show?

              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 8A tail call is a call that is the very last thing a function does. Most engines don't save stack space for it. Which rule is safest?

              Choose an answer to see the explanation.

            Key takeaways

            • Recursion means a function calls itself to solve a smaller version of the same problem.
            • Every recursive function needs a base case it can reach, and a recursive case that moves closer to it.
            • The call stack grows while calls wait. It shrinks as each call returns.
            • If the base case is missing or never reached, the stack fills up and JavaScript throws a RangeError.
            • For simple repeats, a loop is usually better. For trees and other data inside data, recursion is often clearer.

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

            Coming next: Object basics, where you learn the objects used in this lesson's tree examples.

            CompleteFrontend Clear concepts. Working examples.