DEV Community

Cover image for Understanding Recursion in JavaScript — Explained in Simple Words
SAURAV KUMAR
SAURAV KUMAR

Posted on

Understanding Recursion in JavaScript — Explained in Simple Words

If you are learning DSA, recursion can feel scary at first.

When I started learning it, I also felt like:

“Why is a function calling itself?”
“How do I know when it will stop?”
“Why does this look so confusing?”

But the truth is:

Recursion is not hard because of the logic.

It feels hard because we try to understand everything at once.

So in this post, let’s not do that.

Let’s understand recursion in very simple words, step by step, using JavaScript.


What is recursion?

Recursion means:

a function calls itself

That’s the full idea.

A function does some work, and then calls itself again for a smaller problem.


First, let’s remember what a function is

A function is just a block of code that does some work.

Example:

function sayHi() {
  console.log("Hi");
}

sayHi();
Enter fullscreen mode Exit fullscreen mode

Output:

Hi;
Enter fullscreen mode Exit fullscreen mode

Simple.

Now let’s see recursion.


A function calling itself

function hello() {
  console.log("Hello");
  hello();
}
Enter fullscreen mode Exit fullscreen mode

Now think:

What will happen here?

  • it prints Hello
  • then calls itself again
  • then prints Hello
  • then calls itself again
  • again and again

This never stops.

That is the problem.

This is called infinite recursion.


So what do we need?

We need a way to stop the function.

That stopping condition is called the base case.

In simple words:

base case means stop here

Without a base case, recursion keeps running forever.


First simple example: print N to 1

function printNToOne(n) {
  if (n === 0) return;

  console.log(n);
  printNToOne(n - 1);
}

printNToOne(5);
Enter fullscreen mode Exit fullscreen mode

Output:

5;
4;
3;
2;
1;
Enter fullscreen mode Exit fullscreen mode

Now let’s understand it slowly.


Step-by-step dry run

We call:

printNToOne(5);
Enter fullscreen mode Exit fullscreen mode

Step 1

n = 5

Is n === 0?

No.

So print 5.

Then call:

printNToOne(4);
Enter fullscreen mode Exit fullscreen mode

Step 2

n = 4

Is n === 0?

No.

So print 4.

Then call:

printNToOne(3);
Enter fullscreen mode Exit fullscreen mode

Then same thing happens for:

  • 3
  • 2
  • 1

Finally:

printNToOne(0);
Enter fullscreen mode Exit fullscreen mode

Now n === 0 is true.

So function stops.

Done.


Two things every recursive function needs

This is the most important part.

Every recursion problem needs these 2 things:

1. Base case

Where should it stop?

Example:

if (n === 0) return;
Enter fullscreen mode Exit fullscreen mode

2. Smaller problem

How will it move toward the base case?

Example:

printNToOne(n - 1);
Enter fullscreen mode Exit fullscreen mode

So recursion is usually:

  • stop at some point
  • do some work
  • call itself with a smaller value

One very common mistake

Look at this:

function test(n) {
  if (n === 0) return;

  console.log(n);
  test(n + 1);
}
Enter fullscreen mode Exit fullscreen mode

This is wrong.

Why?

Because the base case says stop at 0.

But the recursive call is doing n + 1.

So if n = 5:

  • 5 becomes 6
  • 6 becomes 7
  • 7 becomes 8

It is moving away from 0.

So just having a base case is not enough.

You must also move toward the base case.


Easy rule to remember

Whenever you write recursion, ask:

  1. Where will it stop?
  2. Is every call getting closer to that stop?

If the second answer is no, the recursion is wrong.


Now let’s print 1 to N

This is where many beginners get confused.

function printOneToN(n) {
  if (n === 0) return;

  printOneToN(n - 1);
  console.log(n);
}

printOneToN(5);
Enter fullscreen mode Exit fullscreen mode

Output:

1;
2;
3;
4;
5;
Enter fullscreen mode Exit fullscreen mode

Now the question is:

Why is this printing from 1 to 5?


The simple reason

Because the console.log(n) is written after the recursive call.

That changes the order.

Let’s see:

printOneToN(3);
Enter fullscreen mode Exit fullscreen mode

The function goes like this:

  • printOneToN(3)
  • printOneToN(2)
  • printOneToN(1)
  • printOneToN(0)

At 0, it stops.

Now while coming back:

  • print 1
  • print 2
  • print 3

That is why output becomes:

1;
2;
3;
Enter fullscreen mode Exit fullscreen mode

One small but important idea

If you print before the recursive call:

console.log(n);
printNToOne(n - 1);
Enter fullscreen mode Exit fullscreen mode

you get:

5 4 3 2 1
Enter fullscreen mode Exit fullscreen mode

If you print after the recursive call:

printOneToN(n - 1);
console.log(n);
Enter fullscreen mode Exit fullscreen mode

you get:

1 2 3 4 5
Enter fullscreen mode Exit fullscreen mode

So the position of console.log() matters a lot.


What is happening inside memory?

You will hear this word a lot:

call stack

Do not make it complicated.

Just imagine a stack of plates.

  • a new plate goes on top
  • the top plate comes out first

Same idea here.

When a function calls itself, JavaScript keeps each call in memory.

Example:

function printOneToN(n) {
  if (n === 0) return;

  printOneToN(n - 1);
  console.log(n);
}
Enter fullscreen mode Exit fullscreen mode

If we call:

printOneToN(3);
Enter fullscreen mode Exit fullscreen mode

Then calls are stored like this:

  • printOneToN(3)
  • printOneToN(2)
  • printOneToN(1)
  • printOneToN(0)

Then 0 stops.

Now the stack starts coming back:

  • return to 1 → print 1
  • return to 2 → print 2
  • return to 3 → print 3

That is why recursion sometimes prints while coming back.


Head recursion and tail recursion

These names sound big.

But the idea is small.

It only depends on where the work happens.


Head recursion

In head recursion:

  • recursive call happens first
  • work happens later

Example:

function headRecursion(n) {
  if (n === 0) return;

  headRecursion(n - 1);
  console.log(n);
}

headRecursion(5);
Enter fullscreen mode Exit fullscreen mode

Output:

1;
2;
3;
4;
5;
Enter fullscreen mode Exit fullscreen mode

Why?

Because function first goes deep.

Then it prints while coming back.


Tail recursion

In tail recursion:

  • work happens first
  • recursive call happens after that

Example:

function tailRecursion(n) {
  if (n === 0) return;

  console.log(n);
  tailRecursion(n - 1);
}

tailRecursion(5);
Enter fullscreen mode Exit fullscreen mode

Output:

5;
4;
3;
2;
1;
Enter fullscreen mode Exit fullscreen mode

Why?

Because it prints first, then goes deeper.


Easiest way to remember

Head recursion

function demo(n) {
  if (n === 0) return;
  demo(n - 1);
  console.log(n);
}
Enter fullscreen mode Exit fullscreen mode

Work happens later.

Tail recursion

function demo(n) {
  if (n === 0) return;
  console.log(n);
  demo(n - 1);
}
Enter fullscreen mode Exit fullscreen mode

Work happens first.

That’s it.


Parameterized recursion

Sometimes beginners write recursion using outside variables.

Example:

let count = 1;

function printOneToN(n) {
  if (count > n) return;

  console.log(count);
  count++;
  printOneToN(n);
}
Enter fullscreen mode Exit fullscreen mode

This works.

But it depends on an outside variable.

A cleaner way is to pass everything in parameters.

That is called parameterized recursion.

Example:

function printOneToN(i, n) {
  if (i > n) return;

  console.log(i);
  printOneToN(i + 1, n);
}

printOneToN(1, 5);
Enter fullscreen mode Exit fullscreen mode

Output:

1;
2;
3;
4;
5;
Enter fullscreen mode Exit fullscreen mode

Why is this better?

  • no outside variable
  • cleaner code
  • easier to understand
  • easier to control

In simple words:

parameterized recursion means passing the changing values inside the function parameters


Example: print X, N times

function printXNTimes(x, n) {
  if (n === 0) return;

  console.log(x);
  printXNTimes(x, n - 1);
}

printXNTimes(4, 5);
Enter fullscreen mode Exit fullscreen mode

Output:

4;
4;
4;
4;
4;
Enter fullscreen mode Exit fullscreen mode

Very simple:

  • print x
  • reduce n
  • stop when n becomes 0

Recursion that returns values

Till now, we printed values.

Now let’s see recursion that returns values.

This is very common in DSA.


Example 1: Sum of first N numbers

We know:

sumOfN(5) = 1 + 2 + 3 + 4 + 5
Enter fullscreen mode Exit fullscreen mode

So we can think like this:

sumOfN(5) = 5 + sumOfN(4)
Enter fullscreen mode Exit fullscreen mode

That gives us:

function sumOfN(n) {
  if (n === 0) return 0;

  return n + sumOfN(n - 1);
}

console.log(sumOfN(5));
Enter fullscreen mode Exit fullscreen mode

Output:

15;
Enter fullscreen mode Exit fullscreen mode

Example 2: Factorial

We know:

5! = 5 * 4 * 3 * 2 * 1
Enter fullscreen mode Exit fullscreen mode

So the recursive idea is:

factorial(5) = 5 * factorial(4)
Enter fullscreen mode Exit fullscreen mode

Code:

function factorial(n) {
  if (n === 1) return 1;

  return n * factorial(n - 1);
}

console.log(factorial(5));
Enter fullscreen mode Exit fullscreen mode

Output:

120;
Enter fullscreen mode Exit fullscreen mode

Example 3: Power

Let’s say we want:

2^4 = 16
Enter fullscreen mode Exit fullscreen mode

Then we can think:

power(a, b) = a * power(a, b - 1)
Enter fullscreen mode Exit fullscreen mode

Base case:

power(a, 0) = 1
Enter fullscreen mode Exit fullscreen mode

Code:

function power(a, b) {
  if (b === 0) return 1;

  return a * power(a, b - 1);
}

console.log(power(2, 4));
Enter fullscreen mode Exit fullscreen mode

Output:

16;
Enter fullscreen mode Exit fullscreen mode

Time complexity of simple recursion

Let’s take this function:

function printNToOne(n) {
  if (n === 0) return;
  console.log(n);
  printNToOne(n - 1);
}
Enter fullscreen mode Exit fullscreen mode

If n = 5, it runs about 5 times.

If n = 100, it runs about 100 times.

So time complexity is:

O(n);
Enter fullscreen mode Exit fullscreen mode

Because the function runs n times.


Space complexity of simple recursion

In recursion, memory is used in the call stack.

For this same function, calls stay in memory until the base case is reached.

So stack depth also becomes about n.

That means space complexity is:

O(n);
Enter fullscreen mode Exit fullscreen mode

Simple way to remember:

  • time = how many times function runs
  • space = how many function calls are sitting in stack

How to solve recursion problems step by step

This is the most useful part.

Whenever you see a recursion question, ask these 3 things:

1. What is the base case?

Where should it stop?

2. What is the smaller problem?

How can I reduce the input?

3. What is the relation?

If the smaller answer is already known, how do I use it?

That’s the full thinking process.


My simple recursion template

Before writing code, think like this:

// 1. Base case:
// when should it stop?

// 2. Smaller problem:
// what smaller input should I send?

// 3. Relation:
// how do I use the smaller answer?
Enter fullscreen mode Exit fullscreen mode

This one habit makes recursion much easier.


Common beginner mistakes

Here are some mistakes that happen a lot:

1. No base case

Then recursion never stops.

2. Wrong base case

Then it stops too early or too late.

3. Moving in the wrong direction

Like doing n + 1 when you should do n - 1.

4. Mixing printing and returning

Printing and returning are not the same thing.

5. Using outside variables when not needed

Sometimes parameters make recursion cleaner.


Final thoughts

If recursion feels confusing, that is normal.

It confuses many people in the beginning.

The good thing is this:

Once you understand these 2 ideas, recursion becomes much easier:

  • where to stop
  • how to make the problem smaller

That’s the heart of recursion.

You do not need to learn everything in one day.

Start with small problems:

  • print N to 1
  • print 1 to N
  • sum of first N
  • factorial
  • power

Practice these a few times.

After that, recursion will start feeling much more natural.

If you are also learning DSA right now, spend time on the basics first.

A strong base makes the harder problems much easier later.

Thanks for reading.


👋 About Me

Hi, I’m Saurav Kumar.

I enjoy learning, building, and writing about web development in simple words, especially topics that are useful for beginners and developers preparing for interviews.

Right now, I’m also focusing on improving my understanding of core concepts like JavaScript, OOP, system design, and software engineering fundamentals.

Let's connect!

🐙 GitHub: @saurav02022
💼 LinkedIn: Saurav Kumar

If you found this helpful, share it with a friend learning JavaScript — it might help them too.

Until next time, keep coding and keep learning 🚀

Top comments (0)