Skip to content
Explainer ยท Data Structures and Algorithms

What Is Recursion? A Simple Explanation With Code Examples in Python, JavaScript and Java

By Younus FardeenUpdated 4 Nov 20267 min read
Short answer

Recursion is when a function solves a problem by calling itself on a smaller version of the same problem until it reaches a base case it can answer directly. Every recursive function needs a base case that stops the calls and a recursive case that moves toward it. Without a base case the calls never stop and the program crashes with a stack overflow.

Recursion trips up most beginners because the function seems to use itself before it is finished. The trick is to stop tracing every call and trust one idea: if the function works for a smaller input, you can use that answer to solve the bigger one.

  1. 1Checkis this the base case?
  2. 2Stopreturn the answer
  3. 3Shrinkmake the problem smaller
  4. 4Callcall yourself
  5. 5Combineuse the result
Every recursive function does these five things, in this order.

Recursion in one picture

Think of Russian nesting dolls. To find the smallest doll you open the current one: if it is solid you are done (the base case), otherwise you open the doll inside it and repeat (the recursive case). The task stays the same at every size; only the doll gets smaller.

In code the same shape looks like this. factorial(5) means 5 x 4 x 3 x 2 x 1, and each call hands a smaller factorial to the next:

def factorial(n):
    if n == 0 or n == 1:      # base case
        return 1
    return n * factorial(n - 1)   # recursive case: a smaller problem

print(factorial(5))

Output: 120

The two parts every recursive function needs

  • Base case. The smallest input you can answer without calling yourself, such as an empty list or n = 0. It stops the recursion.
  • Recursive case. The part that calls the function again on a smaller or simpler input, then uses the result.
  • Progress. Each call must move closer to the base case. If the input never shrinks, the base case is never reached.

How the call stack runs factorial(4)

Each call waits on the call below it. The calls pile up on the call stack until the base case returns, then the answers unwind back up:

CallWaits forReturns
factorial(4)4 * factorial(3)24
factorial(3)3 * factorial(2)6
factorial(2)2 * factorial(1)2
factorial(1)nothing: base case1

The deepest call (factorial(1)) returns first. That is why a missing base case ends in a stack overflow: the pile of waiting calls never stops growing.

Five recursion examples

1. Sum of a list (Python)

def total(numbers):
    if not numbers:                 # base case: nothing left to add
        return 0
    return numbers[0] + total(numbers[1:])

print(total([1, 2, 3, 4]))

Output: 10

2. Reverse a string (Python)

def reverse(text):
    if len(text) <= 1:              # base case
        return text
    return reverse(text[1:]) + text[0]

print(reverse("masai"))

Output: iasam

3. Flatten a nested list (Python)

Recursion fits nested data naturally, because a nested list is a list made of smaller lists.

def flatten(items):
    result = []
    for item in items:
        if isinstance(item, list):
            result.extend(flatten(item))   # recurse into the nested list
        else:
            result.append(item)
    return result

print(flatten([1, [2, [3, 4]], 5]))

Output: [1, 2, 3, 4, 5]

4. Fibonacci, slow and fast (JavaScript)

The plain version calls itself twice per step and repeats work. Storing results in a Map (memoization) fixes it.

function fib(n) {
  if (n <= 1) return n;            // base cases: fib(0) = 0, fib(1) = 1
  return fib(n - 1) + fib(n - 2);
}

const memo = new Map();
function fibFast(n) {
  if (n <= 1) return n;
  if (memo.has(n)) return memo.get(n);
  const value = fibFast(n - 1) + fibFast(n - 2);
  memo.set(n, value);
  return value;
}

console.log(fib(10), fibFast(50));

Output: 55 12586269025

5. Fast power (JavaScript)

Halving the exponent each call makes this take about log n steps instead of n.

function power(base, exponent) {
  if (exponent === 0) return 1;                 // base case
  const half = power(base, Math.floor(exponent / 2));
  return exponent % 2 === 0 ? half * half : half * half * base;
}

console.log(power(2, 10), power(3, 5));

Output: 1024 243

Bonus: Tower of Hanoi (Python)

Moving n discs needs 2^n - 1 moves, so 3 discs need 7.

def hanoi(n, source, target, spare, moves):
    if n == 0:
        return
    hanoi(n - 1, source, spare, target, moves)
    moves.append(f"{source}->{target}")
    hanoi(n - 1, spare, target, source, moves)

moves = []
hanoi(3, "A", "C", "B", moves)
print(len(moves), moves)

Output: 7 ['A->C', 'A->B', 'C->B', 'A->C', 'B->A', 'B->C', 'A->C']

The same idea in Java

public class Recursion {
    static long factorial(int n) {
        if (n <= 1) return 1;            // base case
        return n * factorial(n - 1);     // recursive case
    }

    public static void main(String[] args) {
        System.out.println(factorial(5));   // 120
    }
}

Recursion vs iteration

RecursionIteration (loops)
Best forTrees, graphs, nested data, divide and conquer, backtrackingSimple repetition, counting, processing a list in order
ReadabilityOften shorter and closer to the problemOften longer for tree-shaped problems
MemoryUses one stack frame per call (O(depth))Usually O(1) extra memory
SpeedFunction-call overhead; can repeat work without memoizationUsually a little faster
Main riskStack overflow on very deep inputsInfinite loop if the condition never ends

Anything recursive can be written with a loop and an explicit stack, and the reverse is also true. Choose recursion when the problem is naturally self-similar, and a loop when it is just repetition.

Python limits recursion depth, which you can see here:

import sys

def count_down(n):
    if n == 0:
        return 0
    return count_down(n - 1)

print(sys.getrecursionlimit())
try:
    count_down(5000)
except RecursionError:
    print("RecursionError: maximum recursion depth exceeded")

Output: 1000 then the RecursionError message. Python and Java do not optimise tail calls, so very deep recursion should become a loop. In Java the equivalent failure is a StackOverflowError.

Time and space complexity of common recursions

RecursionTimeStack spaceNote
factorial(n)O(n)O(n)One call per step
Fibonacci, plainO(2^n)O(n)Repeats the same subproblems
Fibonacci with memoizationO(n)O(n)Each value is computed once
Fast powerO(log n)O(log n)The exponent halves each call
Binary search (recursive)O(log n)O(log n)Half of the range each call
Merge sortO(n log n)O(log n) frames plus the merge bufferDivide and conquer
Tower of HanoiO(2^n)O(n)2^n - 1 moves

How to think recursively in four steps

  1. Say in one sentence what the function returns for any input, for example: returns the sum of every number in the list.
  2. Write the smallest case you can answer directly. That is the base case.
  3. Assume the function already works for a smaller input.
  4. Use that answer to build the answer for the current input.

This is the same logic as mathematical induction. If you can prove the base case and show that n follows from n - 1, the whole chain is correct.

Common mistakes

  • No base case, or one that is never reached. The program ends in a stack overflow.
  • The input does not shrink. Calling f(n) from f(n) is an infinite loop.
  • Forgetting to return the recursive result. The call runs but the answer is lost.
  • Repeating work. Overlapping calls, as in plain Fibonacci, need memoization or dynamic programming.
  • Copying large lists in every call. Slicing in Python costs O(n) each time; pass an index instead when speed matters.

Where recursion is used in real software

  • Walking a folder tree, where every folder can contain more folders.
  • Parsing nested JSON, HTML or expressions.
  • Tree and graph traversal, including depth-first search.
  • Divide-and-conquer sorting, such as merge sort and quicksort.
  • Backtracking: solving sudoku, N-Queens and generating permutations.

Practice problems, in order

  1. Fibonacci Number (warm-up)
  2. Climbing Stairs (recursion, then memoization)
  3. Pow(x, n) (divide and conquer)
  4. Maximum Depth of Binary Tree (recursion on trees)
  5. Reverse Linked List (recursive version)
  6. Generate Parentheses (backtracking)
  7. Subsets and Permutations (backtracking)

Trace the first few by hand on paper, or watch the stack in Python Tutor. For a longer walk-through, read the Masai guide on how recursion works and recursion vs iteration.

Frequently asked questions

What is recursion in simple words?

A function that solves a problem by calling itself with a smaller piece of the same problem, until the piece is small enough to answer directly.

What is a base case in recursion?

The simplest input, such as zero or an empty list, that the function answers without calling itself. It is what stops the recursion.

Is recursion faster than a loop?

Usually not. A loop avoids the cost of function calls and stack frames. Recursion is chosen because the code is simpler for tree-shaped problems, not because it is faster.

What causes a stack overflow in recursion?

Too many nested calls, usually from a missing or unreachable base case, or from an input that is simply very deep.

Is recursion asked in interviews?

Yes. It appears directly in tree and backtracking questions and underlies dynamic programming, divide and conquer and graph search.

Learn this with mentors

Programmes for builders and engineers with admissions open: live classes, mentors and project work.

Continue learning

Learning path

Data Structures and Algorithms learning path

Arrays to dynamic programming in order, with a problem plan.

Explainer

Dynamic programming explained

Memoization vs tabulation and five solved examples.

Open →
Free tools

Coding practice websites

Where to practise for interviews, placements and contests.

See the list →12 tools