Recursion Explained: How a Function Can Call Itself Without Going in Circles

Learn what recursion is, how base cases and recursive cases work, and when a loop is the better choice, with simple examples in plain language.

Share on Linkedin Share on WhatsApp

Estimated reading time: 7 minutes

Article image Recursion Explained: How a Function Can Call Itself Without Going in Circles

Recursion sounds mysterious the first time you hear it: a function that calls itself. It feels like a recipe that says “to make this dish, first make this dish.” Yet recursion is one of the most useful ideas in programming, and it works in Python, Ruby, Java, C and nearly every other language. This guide explains how it works, step by step.

What recursion actually means

A recursive function solves a problem by breaking it into a smaller version of the same problem. It keeps doing that until the problem becomes so small that the answer is obvious. Think of a set of nested boxes: you open one box, find a smaller box inside, open that one, and repeat until you reach the last empty box.

Every correct recursive function has two essential parts:

  • Base case: the simplest situation, where the function returns an answer directly and stops calling itself.
  • Recursive case: the step where the function calls itself with a smaller or simpler input, moving closer to the base case.

If you forget the base case, or if the recursive case never gets closer to it, the function keeps calling itself forever and the program eventually crashes with a stack overflow error.

A classic example: factorial

The factorial of a number n (written n!) is the product of all positive integers up to n. So 5! equals 5 × 4 × 3 × 2 × 1, which is 120. Notice the pattern: 5! is just 5 times 4!, and 4! is 4 times 3!, and so on. The base case is 1! = 1 (and 0! = 1 by definition).

def factorial(n):
    if n <= 1:        # base case
        return 1
    return n * factorial(n - 1)   # recursive case

print(factorial(5))  # 120

Each call waits for the next one to finish. The language keeps track of these waiting calls in a structure called the call stack.

Following the call stack

Here is what happens when you call factorial(3):

  1. factorial(3) needs factorial(2), so it pauses.
  2. factorial(2) needs factorial(1), so it pauses too.
  3. factorial(1) hits the base case and returns 1.
  4. factorial(2) resumes and returns 2 × 1 = 2.
  5. factorial(3) resumes and returns 3 × 2 = 6.

The calls “stack up” on the way down and then resolve on the way back up. Drawing this on paper is the best way to build intuition, and it is also a great debugging technique.

Another example: summing a list

Recursion shines when data has a nested or repeated structure. To sum a list, you can say: the sum of an empty list is 0, and the sum of any other list is the first element plus the sum of the rest.

def total(items):
    if not items:          # base case: empty list
        return 0
    return items[0] + total(items[1:])

print(total([4, 8, 15]))  # 27

For plain lists, a loop is usually simpler and faster. But the same thinking becomes very powerful with structures like folders inside folders, comment threads with replies, or menus with submenus, where you do not know in advance how deep things go.

Recursion versus loops

Anything you can do with recursion you can also do with a loop, and the reverse is mostly true as well. The choice is about clarity and cost.

AspectRecursionLoop
Best forNested or tree-like data, divide-and-conquer problemsSimple repetition over a known range or list
ReadabilityOften shorter and closer to the problem definitionOften more explicit and easy to trace
MemoryEach call uses stack spaceUsually constant extra memory
Main riskMissing base case or very deep callsInfinite loop from a bad condition

Some languages optimize certain recursive calls, but you should not count on that in every language. Python, for example, limits recursion depth by default, so very deep recursion can raise an error.

Common mistakes and how to avoid them

  • No base case. Always write the stopping condition first.
  • Not shrinking the problem. Make sure each call moves toward the base case, for example with n - 1.
  • Repeating work. A naive recursive Fibonacci recalculates the same values many times. Storing results (memoization) fixes this.
  • Using recursion where a loop is clearer. If a simple for does the job, prefer it.

A simple way to think recursively

When facing a new problem, ask three questions. What is the smallest version of this problem, and what is its answer? How can I reduce a bigger version to a slightly smaller one? If I trust that the function already works for the smaller version, how do I use that result to solve the current one? That “leap of faith” is the key mental shift. You do not trace every call in your head; you trust the function for the smaller case and just handle one step.

Where you will meet recursion in real projects

Recursion appears more often than beginners expect. Searching a folder and all of its subfolders is naturally recursive, because each subfolder is just a smaller folder. The same goes for walking through a family tree, rendering nested comments, parsing a mathematical expression with parentheses, or exploring the possible moves in a puzzle. Classic algorithms such as merge sort and quicksort split a list into smaller parts and solve each part the same way. Learning recursion therefore opens the door to many topics you will meet later, including trees, graphs and dynamic programming.

Conclusion

Recursion is simply a function solving a small piece of a problem and handing the rest to a smaller copy of itself, with a base case to stop the process. Once the idea clicks, you will see it in sorting algorithms, file searches and tree structures. If you want to practice with guided exercises in several languages, explore the programming courses on Cursa and build your skills step by step.

NTFS, exFAT, FAT32 and APFS: Choosing the Right File System for a Drive

Understand what a file system does and how NTFS, exFAT, FAT32, APFS and ext4 differ, so you can format drives without losing compatibility.

Text Encoding Explained: ASCII, Unicode and Why You Sometimes See Strange Symbols

Learn how computers store text, what ASCII and Unicode actually are, why UTF-8 became the standard, and how to fix files that display garbled characters.

Idempotency in APIs: Why Retrying a Request Should Be Safe

Learn what idempotency means in backend development, which HTTP methods provide it, and how idempotency keys prevent duplicate operations.

What Is a CDN? How Content Delivery Networks Make Websites Fast

Learn what a CDN is, how edge caching and cache headers work, what a cache hit means, and when a CDN helps — or does not.

Semantic Versioning Explained: What a Number Like 2.4.1 Actually Tells You

MAJOR.MINOR.PATCH is a promise, not decoration. Learn to read version numbers and understand dependency range symbols.

What Is a Virtual Machine? Virtualization Explained for Beginners

Learn what a virtual machine is, how hypervisors work, how VMs differ from containers, and when to use each one.

How HTTPS Works: Certificates, the TLS Handshake and What the Padlock Really Means

A beginner-friendly walkthrough of HTTPS: what TLS certificates prove, how the handshake works, and what the browser padlock does not guarantee.

Big O Notation Explained: How to Talk About Code Efficiency

A beginner-friendly guide to Big O notation: what it measures, the most common complexity classes, and how to reason about the cost of your code.