G27
Self-checkedTier 1 14 min readpds.recursion-call-stack

Recursion and the call stack

How a function that calls itself actually runs. Frames pile up on the stack while the calls go down, and unwind in reverse as the answers come back.

Why it exists

Many problems contain a smaller copy of themselves. The factorial of 4 is 4 times the factorial of 3. A folder contains folders. A tree is a root with smaller trees hanging off it. Recursion lets a function say "solve a smaller version of me, then finish the job".

There is nothing magical about it for the machine. A recursive call is an ordinary function call whose target happens to be the same function. The only new question is bookkeeping: if fact(4) is waiting for fact(3), and that is waiting for fact(2), where does the machine remember what each of them was in the middle of doing?

The answer is the call stack. Every active call owns one block of memory, a stack frame, holding its own copy of the arguments and local variables and the address to return to. Recursion works because each call gets its own frame.

Prerequisites

You should already be comfortable with: a C function receives copies of its arguments, local variables live only while the function runs, and the stack is one region of the process memory next to the code, data and heap.

Precise definitions

  • Recursive function. A function that calls itself. Direct recursion: f calls f. Indirect (mutual) recursion: f calls g, and g calls f.
  • Base case. An input for which the function returns without making a recursive call. Without one, the recursion never stops.
  • Recursive case. An input for which the function calls itself on a smaller input, meaning one closer to a base case (not always numerically smaller). Every chain of recursive cases must reach a base case.
  • Stack frame (activation record). The block of memory belonging to one call. In the simple textbook model it holds the parameters, the local (automatic) variables and the return address. Real compilers also save the caller's frame pointer and some registers.
  • Active call. A call that has started and not yet returned. Frames exist only for active calls.
  • Recursion depth. The largest number of simultaneously active calls. This page counts it in frames, so fact(4) reaches depth 4. Stack space is about depth×frame size\text{depth} \times \text{frame size} when every frame has the same size.
  • Winding and unwinding. The calls go down (each pushes a frame), then the returns come back up (each pops its frame). The last call made is the first to finish.

Rules that GATE questions rely on:

  1. Parameters and automatic locals are per call. Two active calls of the same function have two independent copies.
  2. static locals and globals exist once. All frames share them.
  3. Arguments are passed by value. Changing n inside a call never changes the caller's n. (If a pointer is passed, the pointed-to object is shared.)
  4. If the base case is never reached, frames keep piling up until the stack is exhausted. The C standard does not define what then happens. On typical systems the program crashes with a stack overflow, although an optimising compiler that turns a tail call into a jump may instead make the program loop forever.

How it works

Watch fact(4). Step forward with the arrow keys or the buttons. Notice the three things that change together: the stack on the left grows, the recursion tree in the middle fills in, and the highlighted line of the C source shows what the top frame is doing.

Call stack: factorial: fact(4)

Real execution trace

C source

1int fact(int n) {
2 if (n <= 1)
3 return 1;
4 return n * fact(n - 1);
5}

GATE model: a frame is parameters + locals + return address.
Real hardware: the compiler also saves the frame pointer and aligns the frame.

Call stacktop ↑

main()

Recursion tree

Trace produced by: real execution of an instrumented C program. gcc (Debian 14.2.0-19) 14.2.0, linux x64. gcc -O0 -fno-omit-frame-pointer -fno-stack-protector -o fact4 fact4.c && ./fact4

Step 0

Execution starts in main, which is about to call fact(4). The stack holds only main's frame.

0 / 12

Stack depth now

0

Max depth so far

0

Calls so far

0

Frame gap, real x86-64

48 B

Read the animation as two phases:

  1. Winding (calls). fact(4) is not a base case, so it calls fact(3) and waits. Its frame stays on the stack with 4 * fact(3) = ? pending. The same happens for fact(3) and fact(2). Four frames are alive at the deepest point.
  2. Unwinding (returns). fact(1) hits the base case and returns 1. Its frame is popped. fact(2) resumes, computes 2×12 \times 1, and returns 2, and so on up to 24.

The number printed under "Frame gap, real x86-64" is a real measurement. In this trace consecutive frames are 48 bytes apart. That figure comes from the instrumented build used to record the trace (the instrumentation adds a local variable and a call to printf), so an un-instrumented program can differ. The GATE model never asks you to guess it: a question that needs a frame size will state it.

Now a function with two recursive calls. fib(4) shows why the tree and the stack are different things. The tree records every call that ever happened. The stack holds only the calls that are active right now, which is always one path from the root down to the current call.

Call stack: Fibonacci: fib(4)

Real execution trace

C source

1int fib(int n) {
2 if (n < 2)
3 return n;
4 return fib(n - 1) + fib(n - 2);
5}

GATE model: a frame is parameters + locals + return address.
Real hardware: the compiler also saves the frame pointer and aligns the frame.

Call stacktop ↑

main()

Recursion tree

Trace produced by: real execution of an instrumented C program. gcc (Debian 14.2.0-19) 14.2.0, linux x64. gcc -O0 -fno-omit-frame-pointer -fno-stack-protector -o fib4 fib4.c && ./fib4

Step 0

Execution starts in main, which is about to call fib(4). The stack holds only main's frame.

0 / 31

Stack depth now

0

Max depth so far

0

Calls so far

0

Frame gap, real x86-64

48 B

Two facts to take from this animation:

  • The stack never holds more than 4 frames, the length of the longest root-to-leaf path.
  • The tree has 9 nodes. The total work is the whole tree, but the total space is only one path.

Worked examples

EX 1Factorial and the unwinding order

Easy
#include <stdio.h>
 
int fact(int n) {
    if (n <= 1) return 1;
    return n * fact(n - 1);
}
 
int main(void) {
    printf("%d\n", fact(4));
    return 0;
}

Output:

24

Step 1. fact(4): 4>14 > 1, so it needs fact(3). Pending: 4×?4 \times ?.

Step 2. fact(3) needs fact(2). Pending: 3×?3 \times ?. Then fact(2) needs fact(1). Pending: 2×?2 \times ?.

Step 3. fact(1): base case, returns 1. Four frames were alive at once.

Step 4. Unwinding: fact(2) returns 2×1=22 \times 1 = 2, fact(3) returns 3×2=63 \times 2 = 6, fact(4) returns 4×6=244 \times 6 = 24.

Calls made: 4. Maximum depth: 4. Time Θ(n)\Theta(n). Stack space Θ(n)\Theta(n). (As a program, fact with a 32-bit int is only meaningful up to n=12n = 12; 13!13! overflows.)

EX 2Before the call, or after the call?

Medium
#include <stdio.h>
 
void f(int n) {
    if (n == 0) return;
    printf("%d ", n);
    f(n - 1);
    printf("%d ", n);
}
 
int main(void) {
    f(3);
    printf("\n");
    return 0;
}

Output:

3 2 1 1 2 3

Step 1. Descent. f(3) prints 3 and calls f(2). That prints 2 and calls f(1). That prints 1 and calls f(0), which returns immediately. The first three numbers printed are 3 2 1.

Step 2. Ascent. f(1) resumes after its call and prints its own n: 1. Then f(2) resumes and prints 2. Then f(3) prints 3.

Step 3. The output is 3 2 1 followed by 1 2 3.

Rule. Work done before the recursive call runs in the order of the calls. Work done after it runs in the reverse order, because the last call is the first to finish. The maximum depth is 4 (f(3), f(2), f(1), f(0)).

EX 3Counting calls and depth in naive Fibonacci

Medium

Let C(n)C(n) be the number of calls made by fib(n). The function makes one call for itself plus the calls of its two children:

C(n)=C(n−1)+C(n−2)+1(n≥2),C(0)=C(1)=1.C(n) = C(n-1) + C(n-2) + 1 \quad (n \ge 2), \qquad C(0) = C(1) = 1.

So C(2)=3C(2) = 3, C(3)=5C(3) = 5, C(4)=9C(4) = 9, C(5)=15C(5) = 15, C(6)=25C(6) = 25. In closed form C(n)=2Fn+1−1C(n) = 2F_{n+1} - 1 with F1=F2=1F_1 = F_2 = 1. The deepest chain is fib(n), fib(n-1), ..., fib(1), so the depth is nn for n≥1n \ge 1.

#include <stdio.h>
 
int calls, depth, maxdepth;
 
int fib(int n) {
    calls++;
    depth++;
    if (depth > maxdepth) maxdepth = depth;
    int r = (n < 2) ? n : fib(n - 1) + fib(n - 2);
    depth--;
    return r;
}
 
int main(void) {
    for (int n = 4; n <= 6; n++) {
        calls = depth = maxdepth = 0;
        fib(n);
        printf("%d %d\n", calls, maxdepth);
    }
    return 0;
}

Output (calls, then maximum depth):

9 4
15 5
25 6

Conclusion. Time is exponential, Θ(φn)\Theta(\varphi^{n}) with φ=1+52\varphi = \tfrac{1+\sqrt5}{2}. Space is only Θ(n)\Theta(n). These are different measures and GATE loves to ask both in one question.

EX 4A halving recursion

GATE level

What does this program print?

#include <stdio.h>
 
int f(int x) {
    if (x <= 0) return 0;
    return f(x / 2) + 1;
}
 
int main(void) {
    printf("%d\n", f(100));
    return 0;
}

Output:

7

Step 1. Follow the argument. Integer division truncates. The arguments are 100, 50, 25, 12, 6, 3, 1, 0. That is 8 calls. The last one (x = 0) is the base case and returns 0.

Step 2. Unwind. Each of the other 7 calls adds 1 on the way back. So f(100)=7f(100) = 7.

Step 3. Generalise. For x≥1x \ge 1 the answer is ⌊log⁡2x⌋+1\lfloor \log_2 x \rfloor + 1. Check: ⌊log⁡2100⌋=6\lfloor \log_2 100 \rfloor = 6, and 6+1=76 + 1 = 7. The number of calls (and so the maximum depth) is ⌊log⁡2x⌋+2\lfloor \log_2 x \rfloor + 2, which is 8 here.

Time and space are both Θ(log⁡x)\Theta(\log x).

EX 5Static versus automatic variables

GATE level
#include <stdio.h>
 
int g(int n) {
    static int calls = 0;
    calls++;
    if (n == 0) return calls;
    return g(n - 1);
}
 
int main(void) {
    printf("%d ", g(3));
    printf("%d\n", g(3));
    return 0;
}

Output:

4 8

Step 1. calls is static, so there is exactly one copy, shared by every frame and kept between separate calls from main.

Step 2. The first g(3) makes 4 calls (n = 3, 2, 1, 0), so calls reaches 4. The base case returns calls, and every frame passes that value upward. The first printf shows 4.

Step 3. The second g(3) makes 4 more calls. calls was not reset, so it ends at 8.

If calls were an ordinary local, each frame would have its own copy starting at 0 and the answer would be completely different. Always ask: is this variable per call, or shared?

Cheat box

Cheat box

  • Stack space ≈max depth×frame size\approx \text{max depth} \times \text{frame size}. It depends on the depth, not on the total number of calls.
  • fact(n): nn calls, depth nn, time Θ(n)\Theta(n), space Θ(n)\Theta(n).
  • Naive fib(n): calls C(n)=2Fn+1−1C(n) = 2F_{n+1} - 1, depth nn, time Θ(φn)\Theta(\varphi^n), space Θ(n)\Theta(n), with φ≈1.618\varphi \approx 1.618.
  • f(x/2) style halving: ⌊log⁡2x⌋+2\lfloor \log_2 x \rfloor + 2 calls for x≥1x \ge 1, time and space Θ(log⁡x)\Theta(\log x).
  • Matching recurrences: T(n)=T(n−1)+c⇒Θ(n)T(n) = T(n-1) + c \Rightarrow \Theta(n). T(n)=2T(n−1)+c⇒Θ(2n)T(n) = 2T(n-1) + c \Rightarrow \Theta(2^n). T(n)=T(n/2)+c⇒Θ(log⁡n)T(n) = T(n/2) + c \Rightarrow \Theta(\log n).
  • Code before the recursive call runs in call order. Code after it runs in reverse order.
  • Parameters and automatic locals: one copy per frame. static and globals: one copy for the whole program.

Traps and GATE patterns

Other things examiners twist:

  • Missing return on the recursive branch. Using the result of a non-void function that fell off the end without returning is undefined behaviour.
  • Pass by value: modifying the parameter inside a call does not change the caller's variable. With a pointer parameter the pointer itself is still a copy, but the callee can change the pointed-to object, which is shared.
  • Mutual recursion: two functions alternate frames on the stack. Draw the frames in order.

Connections

Where to look next: the frame layout here is the idea behind activation records in the compiler, the stack segment is part of the process address space, and the cost formulas are recurrence relations. When naive recursion recomputes the same subproblem again and again, memoization trades extra space for far less time.

PYQs

9 previous year questions from your book. I re-solved each one myself (8 by running or simulating, 1 by reasoning only); the book's key is shown next to my answer. All PYQs

GATE 2019 · MCQ · 2 marksQ6 · page 11 of the book
Consider the following C function.

void convert (int n)
{
    if (n < 0)
    printf ("%d", n);
    else {

convert (n/2);

printf ("%d", n%2);
            }
}

Which one of the following will happen when the
function convert is called with any positive integer n
as argument?
  • (a)It will print the binary representation of n in the reverse order and terminate
  • (b)It will not print anything and will not terminate
  • (c)It will print the binary representation of n and terminate
  • (d)It will print the binary representation of n but will not terminate
GATE 2026 · Set 1 · NAT · 1 markQ2 · page 17 of the book
Consider the recursive functions represented by the
following code segment:

int bar(int n)

{

if (n == 1)  return 0;

else

return 1 + bar(n/2);

}

int foo(int n) {

if (n == 1)

return 1;

else   return 1 + foo(bar(n));

}

The smallest positive integer n for which foo(n)
returns 5 is ______. (answer in integer)
Note: Assume that the program compiles and runs
successfully.
GATE 2023 · MCQ · 2 marksQ8 · page 18 of the book
Consider the following C program:
int main()
{
    f1();
    f2(2);
    f3();
    return(0);
}
int f1()
{
    return(1);
}

int f2(int x)
{
    f3();
    if (x == 1)
        return f1();
    else
        return (x * f2(x - 1));
}
int f3()
{
    return(5);
}

Which of the following options represent the
activation function corresponding to the main
function?
GATE 2018 · NAT · 1 markQ15 · page 20 of the book
Consider the following C program:
#include <stdio.h>
int counter = 0;
int calc (int a, int b) {
     int c;
     counter++;
     if (b= =3) return (a*a*a) ;
      else  {
                    c = calc (a, b/3) ;
                   return (c*c*c) ;
}
}
int main ()  {
        calc (4, 81);
        printf ("%d", counter) ;
}
The output of this program is ______.
GATE 2017 · Set 2 · MCQ · 2 marksQ18 · page 21 of the book
Consider the following two functions.
void fun1(int n) {
    if(n = = 0) return;
    printf("%d", n);
    fun2(n - 2);
    printf("%d", n);
}
void fun2(int n) {
    if(n = = 0) return;
    printf("%d", n);
    fun1(++n);
    printf("%d", n);
}

The output printed when fun1(5) is called is
  • (a)53423122233445
  • (b)53423120112233
  • (c)53423122132435
  • (d)53423120213243
GATE 2017 · Set 1 · MCQ · 2 marksQ20 · page 21 of the book
Consider the C functions foo and bar given below:

int foo(int val) {
    int x = 0;
    while (val > 0) {
        x = x + foo(val--);
    }
    return val;
}

int bar(int val) {
    int x = 0;
    while (val > 0) {
        x = x + bar(val - 1);
    }
    return val;
}
Invocation of foo(3) and bar(3) will result in:
  • (a)Return of 6 and 6 respectively.
  • (b)Infinite loop and abnormal termination respectively
  • (c)Abnormal termination and infinite loop respectively
  • (d)Both terminating abnormally.
GATE 2014 · Set 2 · MCQ · 1 markQ28 · page 23 of the book
Consider the C function given below.
int f(int j)
{
  static int i = 50;

 int k;

 if (i == j)
  {
    printf("something");
    k = f(i);
    return 0;

  }
  else return 0;
}
Which one of the following is TRUE?
  • (a)The function returns 0 for all values of j.
  • (b)The function prints the string something for all values of j
  • (c)The function returns 0 when j = 50.
  • (d)The function will exhaust the runtime stack or run into an infinite loop when j = 50.
GATE 2014 · Set 3 · MCQ · 1 markQ29 · page 23 of the book
Which of the following statements are CORRECT?
1. Static allocation of all data areas by a compiler
makes it impossible to implement recursion.
2. Automatic garbage collection is essential to
implement recursion.
3. Dynamic allocation of activation records is
essential to implement recursion.
4. Both heap and stack are essential to implement
recursion.
  • (a)1 and 2 only
  • (b)2 and 3 only
  • (c)3 and 4 only
  • (d)1 and 3 only
GATE 2005 · Set 1 · MCQ · 1 markQ44 · page 27 of the book
Consider the following C-program:

void foo(int n, int sum)

{

int k = 0, j = 0;

if (n == 0)  return;

k = n % 10;

j = n / 10;

sum = sum + k;

foo(j, sum);

printf("%d,", k);

}

int main()

{

int a = 2048, sum = 0;

foo(a, sum);

printf("%d\n", sum);

getchar();
}

What does the above program print?
  • (a)8, 4, 0, 2, 14
  • (b)8, 4, 0, 2, 0
  • (c)2, 0, 4, 8, 14
  • (d)2, 0, 4,8, 0

Self-check

Question 1

How many calls does fib(5) make in total, and what is the maximum stack depth (in frames of fib)?

Show answer

15 calls and a maximum depth of 5. From C(n)=C(n−1)+C(n−2)+1C(n) = C(n-1) + C(n-2) + 1: C(4)=9C(4) = 9 and C(3)=5C(3) = 5, so C(5)=9+5+1=15C(5) = 9 + 5 + 1 = 15. The depth is n=5n = 5. Both values are confirmed by the program in Example 3.

Question 2

What does this program print?

#include <stdio.h>
 
void g(int n) {
    if (n > 0) {
        g(n - 1);
        printf("%d ", n);
        g(n - 1);
    }
}
 
int main(void) {
    g(3);
    printf("\n");
    return 0;
}
Show answer

g(1) prints just 1 (its two inner calls are g(0), which does nothing). g(2) prints g(1), then 2, then g(1) again: 1 2 1. g(3) prints g(2), then 3, then g(2) again:

1 2 1 3 1 2 1

Question 3

In Example 4, what is the largest number of frames of f alive at the same time when f(100) runs?

Show answer

8, the number of calls on the single chain 100, 50, 25, 12, 6, 3, 1, 0. Check with a depth counter:

#include <stdio.h>
 
int depth, maxdepth;
 
int f(int x) {
    depth++;
    if (depth > maxdepth) maxdepth = depth;
    int r = (x <= 0) ? 0 : f(x / 2) + 1;
    depth--;
    return r;
}
 
int main(void) {
    f(100);
    printf("%d\n", maxdepth);
    return 0;
}
8

Question 4

Why does naive fib(n) need only O(n)O(n) stack space even though it makes exponentially many calls?

Show answer

Frames exist only for active calls, and the active calls always form a single path from the first call down to the current one. That path has length at most nn. Calls that have already returned have had their frames popped, so they cost no space.

Revision card

One-screen revision

  • A recursive call is a normal call. Each active call owns a frame: parameters, locals, return address.
  • Calls go down (push), returns come up (pop). Last in, first out.
  • Space ≈ depth × frame size. Time = number of calls × work per call.
  • fact(n): depth nn. Naive fib(n): calls 2Fn+1−12F_{n+1}-1, depth nn. Halving: ⌊log⁡2x⌋+2\lfloor \log_2 x \rfloor + 2 calls.
  • Code before the call: call order. Code after the call: reverse order.
  • static and globals are shared; parameters and automatic locals are per frame.
  • No optimisation assumed unless the question says so. Check that the base case can actually be reached.

References

  • ·Kernighan and Ritchie, The C Programming Language, 2nd ed., Section 4.10 Recursion
  • ·Aho, Lam, Sethi, Ullman, Compilers: Principles, Techniques, and Tools, 2nd ed., Section 7.2 Stack Allocation of Space
  • ·Cormen, Leiserson, Rivest, Stein, Introduction to Algorithms, 3rd ed., Chapter 4 Divide-and-Conquer (solving recurrences)

Verification

Every C program on this page is compiled and run with gcc by npm run verify:c, and the displayed outputs match the real outputs. The two call-stack animations replay real execution traces. The prose, the GATE-pattern claims and the cheat-box formulas have NOT yet had a human textbook check (the independent second pass is described at the end of these notes). SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand (long C programs were not re-read line by line: they are compiled and compared by npm run verify:c, and their printed outputs were checked against the prose). No defect found. Status raised to self-checked; verified needs a textbook or human check.

last verified: never