Learn C Programming

Lesson 1 of 7 · Recursion

Module 8 · Recursion

Recursion: a Function That Calls Itself

FreeReading

In this lesson

  • Write a recursive function with a base case and a step: a countdown, a sum to n, a factorial.
  • Trace a recursion with a frame table, and explain why work after the call runs on the way back.
  • Recognise a missing or unreachable base case by what the Playground and gcc -Wall say.

Bob learned in Module 7 that a function can call any other function. Then a bold thought: can a function call itself? He wants 1 + 2 + 3, and he notices that it is 3 plus the sum 1 + 2.

So he writes a function that adds n to its own answer for n - 1, and presses Run. Nothing appears. After 10 seconds the Playground stops the program, and the badge reads Time limit exceeded.

His book promised a dramatic "stack overflow" for a mistake like this. He got a quiet clock instead. This lesson finds the line Bob left out, and what really happened in those 10 seconds.

Recursion: a function that calls itself

A function that calls itself is recursive, and the idea is called recursion. It works when each call hands a smaller version of the same problem to the next call. Here is Bob's program, exactly as he ran it.

#include <stdio.h>

int sum_to(int n);

int main(void)
{
    printf("%d\n", sum_to(3));
    return 0;
}

int sum_to(int n)
{
    return n + sum_to(n - 1);
}

Follow it by hand. sum_to(3) needs sum_to(2), which needs sum_to(1), which needs sum_to(0). Nothing tells sum_to(0) to stop, so it asks for sum_to(-1), then sum_to(-2), with no end.

Think of a long queue at a ticket counter. You want to know your place, so you ask the person in front and add one. They do the same. The chain ends at the first person, who knows without asking: they are number 1.

So a recursive function needs one call that answers without asking anyone. In Bob's program every call asks, and no call ever answers.

The two rules: a base case and a step

Every recursive function that ends follows two rules. The base case is the smallest input, answered directly, with no call. The step calls the same function on a smaller input, one step closer to the base case. Some books call the step the recursive case.

A recursive function

type name(int n)
{
    if (n == smallest) {
        return answer;
    }
    return ... name(n - 1) ...;
}
  • if (n == smallest) is the base-case test. It comes first, before any call.
  • return answer; answers the smallest input with no call, so the chain stops there.
  • name(n - 1) is the step: the same function, on a value closer to the base case.
  • The ... turns the smaller answer into this one, such as n + for a sum.

Bob adds the missing rule. The sum of the numbers up to 0 is 0, so sum_to(0) returns 0 without a call. He also makes the answer a long long, because from n = 65536 the sum passes INT_MAX.

#include <stdio.h>

long long sum_to(int n);

int main(void)
{
    printf("%lld\n", sum_to(3));
    printf("%lld\n", sum_to(100));
    return 0;
}

long long sum_to(int n)
{
    if (n == 0) {
        return 0;
    }
    return n + sum_to(n - 1);
}

Trace it before you run it. Module 6 traced a loop with one row per pass. A recursion gets one row per call: the call, its parameter, what it waits for, and what it returns. Call it a frame table.

CallnWaits forReturns
sum_to(3)3sum_to(2)3 + 3 = 6
sum_to(2)2sum_to(1)2 + 1 = 3
sum_to(1)1sum_to(0)1 + 0 = 1
sum_to(0)0nothing: the base case0

Fill the table in two passes. Go down the first three columns as the calls are made. Then fill the last column from the bottom up: a row can finish only after the row below it returns.

6
5050

So the base case is where the answers start. Without it, as in Bob's program, no row ever returns.

Countdown: work before the call

Bob's rocket game from Module 6 needs its countdown back, this time as a recursion. Counting down from n means: say n, then count down from n - 1. Counting down from 0 means just "liftoff".

#include <stdio.h>

void countdown(int n);

int main(void)
{
    countdown(3);
    return 0;
}

void countdown(int n)
{
    if (n == 0) {
        printf("liftoff\n");
        return;
    }
    printf("%d\n", n);
    countdown(n - 1);
}
CallnPrints before its callThen calls
countdown(3)33countdown(2)
countdown(2)22countdown(1)
countdown(1)11countdown(0)
countdown(0)0liftoff, the base casenothing
3
2
1
liftoff

Each call prints first and calls second, so the numbers appear in the order the calls are made. The base case prints liftoff at the bottom. A void function returns no value, so its base case ends with a plain return;.

So work placed before the call happens on the way down, in the order the calls are made.

Work after the call happens on the way back up

Now put the work below the call. A line after the call cannot run until that call has returned. So lines after the call run in the opposite order, on the way back up.

Alice wants to watch sum_to build its answer. She keeps each total in a local variable and prints it after the call.

#include <stdio.h>

long long sum_to(int n);

int main(void)
{
    long long total = sum_to(3);

    printf("total: %lld\n", total);
    return 0;
}

long long sum_to(int n)
{
    long long total = 0;

    if (n == 0) {
        return 0;
    }
    total = n + sum_to(n - 1);
    printf("sum to %d is %lld\n", n, total);
    return total;
}
CallnWaits forPrints after the waitReturns
sum_to(3)3sum_to(2)sum to 3 is 66
sum_to(2)2sum_to(1)sum to 2 is 33
sum_to(1)1sum_to(0)sum to 1 is 11
sum_to(0)0nothing: the base casenothing0

The calls are made from the top row down, but the prints happen from the bottom row up. sum_to(1) prints first, because it is the first call to get its answer.

sum to 1 is 1
sum to 2 is 3
sum to 3 is 6
total: 6

Every call gets its own boxes for its parameters and local variables (Module 7). That set of boxes is the call's frame. The frames of calls still waiting pile up in a part of memory called the stack, one frame per call. Lesson 2 opens the stack up; here is the picture.

The frames of sum_to(3): calls go down, answers come back up Calls go down, answers come back up main waits for sum_to(3) calls sum_to(3) returns 6 sum_to(3), n = 3 waits for sum_to(2), then adds 3 calls sum_to(2) returns 3 sum_to(2), n = 2 waits for sum_to(1), then adds 2 calls sum_to(1) returns 1 sum_to(1), n = 1 waits for sum_to(0), then adds 1 calls sum_to(0) returns 0 sum_to(0), n = 0 base case: returns 0, makes no call Four frames of sum_to exist at the same time, each with its own n. The answers climb from the bottom frame up: 0, then 1, then 3, then 6 reaches main.

The left arrows are the calls, on the way down. The right arrows are the answers, on the way back up. A frame disappears when its call returns.

So work before the call runs along the left arrows, and work after it along the right ones.

Factorial, with a long long

Module 6 asked you for Kenji's factorial as a loop, and promised it would come back. The factorial of n, written n!, is 1 x 2 x ... x n, and 0! is 1. Read it the recursive way: n! is n times (n - 1)!, and 0! is the base case.

#include <stdio.h>

long long factorial(int n);

int main(void)
{
    for (int i = 0; i <= 20; i++) {
        printf("%d! = %lld\n", i, factorial(i));
    }
    return 0;
}

long long factorial(int n)
{
    if (n == 0) {
        return 1;
    }
    return n * factorial(n - 1);
}

Here is the frame table for one of those calls, factorial(4).

CallnWaits forReturns
factorial(4)4factorial(3)4 x 6 = 24
factorial(3)3factorial(2)3 x 2 = 6
factorial(2)2factorial(1)2 x 1 = 2
factorial(1)1factorial(0)1 x 1 = 1
factorial(0)0nothing: the base case1
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800
11! = 39916800
12! = 479001600
13! = 6227020800
14! = 87178291200
15! = 1307674368000
16! = 20922789888000
17! = 355687428096000
18! = 6402373705728000
19! = 121645100408832000
20! = 2432902008176640000

The answer grows fast. 12! = 479001600 fits an int, but 13! = 6227020800 is past INT_MAX, which is 2147483647. That is why factorial returns a long long.

A long long stops at 20!. LLONG_MAX is 9223372036854775807, and 20! is about a quarter of it. 21! is 21 times 20!, about 5.1 x 10^19, more than five times LLONG_MAX. Working it out is signed overflow, which is undefined behaviour (Module 2), so this lesson never prints it.

So a recursive factorial needs the base case 0! = 1 and a type big enough for the answer: an int up to 12!, a long long up to 20!.

The missing base case, measured

Back to Bob's program. His book says a function with no base case shows the error "Stack overflow". C prints no such message, and on the Playground Bob's program did not crash at all. Here is why.

The Playground compiles with GCC 12 at -O2, which optimises. It rewrites your program into faster machine code that does the same job. GCC 12 saw that each call only adds a number to the next call's answer. So it replaced the calls with a loop, and that loop has no way out.

Here is that machine code, called a listing, as GCC 12 printed it on Compiler Explorer at the Playground's -O2:

main:
.L2:
        jmp     .L2
sum_to:
.L5:
        jmp     .L5

jmp .L2 means "jump to the label .L2", and .L2 is that same line. So main jumps to itself forever and never reaches its printf. No call is left, nothing piles up, and the program spins until the 10 second limit.

What you see depends on what the function does around its call:

The function with no base caseWhat GCC 12 builds at -O2What the Playground shows
Only arithmetic around the call, like Bob's sum_toan endless loop(no output) and Time limit exceeded, after 10090 ms
Prints on every call, like a countdown with no ifan endless loop that printsIt prints until the Playground's 1 MB output cap is full, and the run ends with the badge Output limit exceeded (1 MB).
Work after the call that GCC cannot turn into a loopreal calls, one frame eachThe stack grows until the program reaches its 256,000 KB memory limit: Memory limit exceeded and (no output). Lesson 2 measures where.

The machine code also depends on the build. Build Bob's file with no optimisation, at -O0, and every call gets a real frame. On Compiler Explorer's GCC 12 that build crashed in 193 ms (one run) with Program terminated with signal SIGSEGV (11).

In a Linux terminal the same crash prints Segmentation fault. That is how the operating system reports a program that touched memory it may not use. Lesson 5 explains the signal and the stack's limit.

None of these results mentions recursion, but the compiler can name it. The Playground stays silent. A local gcc -Wall on GCC 12 says warning: infinite recursion detected [-Winfinite-recursion], with a note pointing at the call. That warning is new in GCC 12.

So a missing base case can look like a time limit, an output error or a memory limit. -Wall names the real problem before you run.

Example 1: the smallest recursion that prints

A base case can do nothing at all but stop. This one prints one star per call.

#include <stdio.h>

void stars(int n);

int main(void)
{
    stars(3);
    printf("\n");
    return 0;
}

void stars(int n)
{
    if (n == 0) {
        return;
    }
    printf("*");
    stars(n - 1);
}
CallnPrintsThen calls
stars(3)3*stars(2)
stars(2)2*stars(1)
stars(1)1*stars(0)
stars(0)0nothing: the base casenothing
***

Each call prints one star and hands the rest of the row to a smaller call. The newline comes from main, after the last call has returned.

Run in Compiler
Example 2: Amara's orange pyramid

Amara stacks oranges in a square pyramid at her shop. The top layer holds 1 orange, the next 4, then 9: layer k holds k x k. How many oranges does a pyramid of n layers hold?

#include <stdio.h>

long long pyramid(int layers);

int main(void)
{
    printf("4 layers: %lld oranges\n", pyramid(4));
    printf("10 layers: %lld oranges\n", pyramid(10));
    return 0;
}

long long pyramid(int layers)
{
    if (layers == 0) {
        return 0;
    }
    return layers * layers + pyramid(layers - 1);
}
CalllayersWaits forReturns
pyramid(4)4pyramid(3)16 + 14 = 30
pyramid(3)3pyramid(2)9 + 5 = 14
pyramid(2)2pyramid(1)4 + 1 = 5
pyramid(1)1pyramid(0)1 + 0 = 1
pyramid(0)0nothing: the base case0
4 layers: 30 oranges
10 layers: 385 oranges

The step does not have to add n. Here it adds the bottom layer, then asks for the smaller pyramid resting on it. The total is a long long because it passes INT_MAX from 1861 layers.

Run in Compiler
Example 3: the sum of a range, read from the input

Maria wants the sum of every whole number from a to b. Here the smaller problem is a shorter range: add a, then sum the range from a + 1 to b.

#include <stdio.h>

long long sum_range(int a, int b);

int main(void)
{
    int a = 0;
    int b = 0;

    scanf("%d %d", &a, &b);
    printf("%lld\n", sum_range(a, b));
    return 0;
}

long long sum_range(int a, int b)
{
    if (a > b) {
        return 0;
    }
    return a + sum_range(a + 1, b);
}
CallabWaits forReturns
sum_range(3, 7)37sum_range(4, 7)3 + 22 = 25
sum_range(4, 7)47sum_range(5, 7)4 + 18 = 22
sum_range(5, 7)57sum_range(6, 7)5 + 13 = 18
sum_range(6, 7)67sum_range(7, 7)6 + 7 = 13
sum_range(7, 7)77sum_range(8, 7)7 + 0 = 7
sum_range(8, 7)87nothing: the base case0
25

That output is for the input 3 7. The step moves a up, not down, and the base case is an empty range.

Zara tries the edges first. 5 5 gives 5, a range of one number. 5 3 gives 0 at once, because the range is empty. A base case written as a == b would never meet 5 3, because a only grows.

Run in Compiler

Where this is used

  • Folder trees. A folder's size is its own files plus the size of each folder inside it. GNU coreutils' du --help says it works "recursively for directories", and the -r of rm -r means --recursive.
  • JSON parsers. cJSON, a small C library, reads a JSON value with parse_value. For a list or an object, parse_array or parse_object calls parse_value again for each item. Its CJSON_NESTING_LIMIT of 1000 stops the calls before the stack runs out.
  • GCC's own C parser. Since GCC 4.1 in 2006, GCC reads C with a hand-written recursive-descent parser. An expression inside brackets is read by the same functions as the whole expression, one call deeper.
  • Fractals on screen. Python's turtledemo package has a fractalcurves demo that draws a Koch curve. Its function calls itself four times, each on a line a third as long. At depth 0 it draws a plain line: the base case.

Common mistakes

1. A step that jumps over the base case.

long long every_other(int n)
{
    if (n == 0) {
        return 0;
    }
    return n + every_other(n - 2);
}

No message at any command line. From 6 it works, but from 7 it goes 7, 5, 3, 1, -1 and never lands on 0. GCC 12 at -O2 turned the call with 7 into the same endless jump as Bob's. Test n <= 0, which also catches a step that skips past 0. You will write == 0 because it worked for the first input you tried.

2. No return on the recursive line.

long long sum_to(int n)
{
    if (n == 0) {
        return 0;
    }
    n + sum_to(n - 1);
}

Silent on the Playground. A local gcc -Wall on GCC 12 says warning: value computed is not used [-Wunused-value] and warning: control reaches end of non-void function [-Wreturn-type]. The sum is thrown away, and what main then prints is undefined behaviour. Write return n + sum_to(n - 1);. You will forget the return because the maths reads fine without it.

3. A base case at 1 when 0 is a valid input.

long long factorial(int n)
{
    if (n == 1) {
        return 1;
    }
    return n * factorial(n - 1);
}

No message at any command line when n comes from the input. It is right from 1 to 20, but factorial(0) steps to -1, -2 and on, away from 1. The int finally overflows, which is undefined behaviour, so C promises nothing from there on. One run on Compiler Explorer's GCC 12 at the Playground's -O2, whole program, took 7247 ms for the input 0. It printed a wrong answer.

Put the base case at 0, the smallest allowed input. You will pick 1 because 1! is 1, and the product seems to start there.

Brain teaser

Amara moves one line of the countdown. The printf now comes after the call, and the base case prints nothing.

#include <stdio.h>

void f(int n);

int main(void)
{
    f(3);
    printf("\n");
    return 0;
}

void f(int n)
{
    if (n == 0) {
        return;
    }
    f(n - 1);
    printf("%d ", n);
}

It compiles with no message. What does it print for 3? The countdown printed 3, 2, 1. Why does this one flip the order?

Make a frame table with a column for what each call prints after its call returns. Then fill that column from the bottom row up.

Exercise 1Easy

Bob fixes his sum for good. Write long long sum_to(int n) recursively, and print the sum for any n the player types.

Input. One line with one integer n.

Output. One line with 1 + 2 + ... + n.

Constraints. 0 <= n <= 10000.

Sample. Input 3 gives 6. Input 0 gives 0.

#include <stdio.h>

long long sum_to(int n);

int main(void)
{
    int n = 0;
    scanf("%d", &n);

    printf("%lld\n", sum_to(n));
    return 0;
}

long long sum_to(int n)
{
    /* The base case comes first: what is the sum of the numbers up to 0?
       Then return n plus one call with a smaller n. */
    return 0;
}

Not graded on its own. Module 6's sum-to-n grades this same output, and a judge sees only the output.

Run in Compiler
Exercise 2Medium

Alice prints factorials for the maths club. The members call out numbers until they run out, and she prints the factorial of each one. Write long long factorial(int n), recursively.

Input. Integers separated by spaces or newlines, to the end of input. There is at least one.

Output. For each integer n, one line with n!, in the order they were read.

Constraints. At most 21 integers, each with 0 <= n <= 20.

Sample. Input 0 5 20 gives 1, 120 and 2432902008176640000 on three lines.

#include <stdio.h>

long long factorial(int n);

int main(void)
{
    int n = 0;

    while (scanf("%d", &n) == 1) {
        printf("%lld\n", factorial(n));
    }
    return 0;
}

long long factorial(int n)
{
    /* The base case comes first. Which n is the smallest one allowed?
       Every other n is n times the factorial of n - 1. */
    return 0;
}

Graded as factorials. The hidden tests include 0 on its own, 13 (the first factorial past INT_MAX) and 20.

Run in Compiler
Exercise 3Hard

Bob's countdown goes down to 1, then climbs back up to where it started. He wants it from one function that prints on both sides of its own call. Write void down_and_up(int n).

Input. One line with one integer n.

Output. n, n - 1, ..., 1, then 1, 2, ..., n, one number per line. So 1 appears twice.

Constraints. 1 <= n <= 1000.

Sample. Input 3 gives 3, 2, 1, 1, 2 and 3 on six lines.

#include <stdio.h>

void down_and_up(int n);

int main(void)
{
    int n = 0;
    scanf("%d", &n);

    down_and_up(n);
    return 0;
}

void down_and_up(int n)
{
    /* One function that prints on both sides of its own call.
       How many times must 1 be printed? */
}

Graded as down-and-up. The hidden tests include n = 1, which must print 1 twice, and n = 1000.

Run in Compiler

Common doubts

  • Why did Bob get Time limit exceeded, and not a stack overflow?

    GCC 12 at -O2 turned his recursion into a loop, and a loop needs no new memory per pass. "Stack overflow" is not a message C prints at all. A build with no optimisation does crash, as the -O0 run showed.

  • Is recursion slower than a loop?

    A real call costs a little more than one pass of a loop. But GCC 12 often removes the calls from a simple recursion. On the Playground, the recursive sum_to answered for 100000000 in 70 ms. Lesson 4 measures both.

  • Every call uses the name n. How does each call keep its own n?

    Each call gets a fresh box for its parameter, filled with a copy of the argument (Module 7). So sum_to(3) makes four boxes called n, one per frame. Lesson 2 draws them.

  • Does the base case have to be 0?

    No. It is the smallest input the function must answer. Factorial's is 0, because 0! is a fair question. A function only ever asked about 1 or more may stop at 1.

Key takeaways

  • A recursive function calls itself on a smaller problem, and each call waits for the call it made.
  • It needs a base case that answers with no call, tested first, and a step that moves towards it.
  • A frame table has one row per call: calls go down the rows, answers come back up.
  • Work before the call runs on the way down; work after it runs on the way back, in reverse order.
  • factorial needs a long long: 12! is the last to fit an int, 20! the last to fit a long long.
  • A missing base case looks like a time, output or memory limit; gcc -Wall on GCC 12 names it.

Next, Amara draws the frames on paper, one box per call, and predicts the output before she presses Run.

End of lesson 1

Mark it done, and your progress moves with you.

Next: The Call Stack, Frame by Frame