Skip to main content

Functions

Recursion & Storage Classes

Master C Recursion — what/why, applications, head vs tail recursion, types of recursion — plus Storage Classes (scope, lifetime, local/global, auto, register, static, extern) using LEARNCODEGURU’s 6-level learning model.

Level 1 Basics Content

C Recursion Overview

Recursion is a function calling itself to solve a smaller version of the same problem. This level covers what recursion is, why it is used, its applications, the essential base case and recursive case, head vs tail recursion, types of recursion, and recursion vs iteration.

The idea: solve a big problem by assuming you can already solve a slightly smaller one, and stop at a case so small the answer is obvious.

Topic 1

What is recursion?

A recursive function is one whose body contains a call to itself. Every recursive function needs two parts:

  • Base case — the stopping condition; answers directly without recursing
  • Recursive case — solves the problem using a smaller input

long fact(int n) {
    if (n <= 1)
        return 1;                  /* base case */
    return n * fact(n - 1);    /* recursive case */
}

Tracing fact(4):

fact(4) → 4*fact(3) → 4*3*fact(2) → 4*3*2*fact(1) → 4*3*2*1 = 24
Remember: without a base case the function calls itself forever and the program crashes with a stack overflow.
Topic 2

Why recursion?

  • Natural fit — some problems are defined recursively (factorial, Fibonacci, trees)
  • Shorter, clearer code — mirrors the mathematical definition
  • Divide and conquer — split the problem, solve halves, combine (merge sort, binary search)
  • No manual stack — the call stack tracks state for you
  • Elegant tree/graph traversal — visiting children is just another call
Recursion is about expressing the solution clearly. It is not automatically faster — often a loop is more efficient.
Topic 3

Applications of recursion

# Application Example
1 Mathematical definitions Factorial, power, Fibonacci, GCD
2 Divide and conquer algorithms Binary search, merge sort, quick sort
3 Tree and graph traversal Preorder / inorder / postorder, DFS
4 String / array processing Reverse a string, palindrome check, array sum
5 Backtracking puzzles Towers of Hanoi, N-Queens, maze solving
6 Parsing nested structures Expressions with brackets, directory trees
Recursion in C: a function calling itself with a smaller input, showing the base case that stops the calls, the recursive case, and the stack frames created and unwound for each call.
Recursion — a function calls itself until the base case stops the chain
Topic 4

Head recursion vs tail recursion

The difference is where the recursive call sits relative to the other work.

Head recursion — the call comes first; work happens while returning:


void headPrint(int n) {
    if (n == 0)
        return;
    headPrint(n - 1);        /* recurse FIRST */
    printf("%d ", n);        /* then do the work */
}
/* headPrint(5) prints: 1 2 3 4 5  (ascending) */

Tail recursion — the work comes first; the call is the last action:


void tailPrint(int n) {
    if (n == 0)
        return;
    printf("%d ", n);        /* do the work FIRST */
    tailPrint(n - 1);        /* then recurse — nothing after */
}
/* tailPrint(5) prints: 5 4 3 2 1  (descending) */
Aspect Head recursion Tail recursion
Position of the call First / before the work Last / after the work
Work happens While unwinding (on the way back) Before recursing (on the way down)
Output order Reversed relative to the calls Same order as the calls
Pending work in frames Yes — each frame waits No — nothing left to do
Optimizable to a loop Not directly Yes (compiler may reuse the frame)
Typical use Printing digits / lists in reverse Accumulator-style computation
Quick check: if anything happens after the recursive call returns (a multiplication, a print, an addition), it is not tail recursion. return n * fact(n-1); is head-style because the multiply waits for the call.
Topic 5

Types of recursion

# Type Meaning Example
1 Direct recursion A function calls itself fact() calls fact()
2 Indirect (mutual) recursion Two functions call each other isEven() ↔ isOdd()
3 Head recursion Recursive call before the work Print 1..n ascending
4 Tail recursion Recursive call is the last action Print n..1 descending
5 Linear recursion One recursive call per invocation Factorial, array sum
6 Tree (multiple) recursion Two or more calls per invocation Fibonacci, Towers of Hanoi
7 Nested recursion A call whose argument is itself a call f(f(n)) — Ackermann style

/* Indirect recursion — each needs the other */
int isOdd(int n);          /* forward prototype */

int isEven(int n) {
    if (n == 0) return 1;
    return isOdd(n - 1);
}

int isOdd(int n) {
    if (n == 0) return 0;
    return isEven(n - 1);
}
Topic 6

Recursion vs iteration

Aspect Recursion Iteration (loops)
How repetition happens Function calls itself for / while repeats a block
Termination Base case Loop condition
Memory One stack frame per call Constant — a few variables
Speed Call overhead per step Usually faster
Risk Stack overflow if too deep Infinite loop if condition never fails
Best for Trees, divide and conquer, backtracking Simple counted or sequential repetition
Both can solve the same problems. Choose recursion when it makes the code obviously match the problem; choose iteration when depth is large or performance matters.
Example

Sample program — factorial plus head and tail printing


/* Recursion basics: factorial + head/tail order demo */
#include <stdio.h>

long fact(int n);
void headPrint(int n);
void tailPrint(int n);

int main(void) {
    int n;

    printf("Enter n: ");
    scanf("%d", &n);

    printf("Factorial of %d = %ld\n", n, fact(n));

    printf("Head recursion (ascending): ");
    headPrint(n);
    printf("\n");

    printf("Tail recursion (descending): ");
    tailPrint(n);
    printf("\n");

    return 0;
}

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

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

void tailPrint(int n) {
    if (n == 0)
        return;
    printf("%d ", n);
    tailPrint(n - 1);
}
What to observe: both print functions recurse the same number of times, but the position of printf flips the output order.

Storage Classes in C

A storage class tells the compiler four things about a variable: its scope (where it is visible), its lifetime (how long it exists), its default value when uninitialized, and where it is stored. This closely relates to recursion, because each recursive call gets fresh automatic variables but shares any static ones.

Topic 7

Local vs global variables

# Kind Declared where Visible where Lives how long Default value
1 Local (automatic) Inside a function or block Only in that block Until the block/function ends Garbage (indeterminate)
2 Global (external) Outside all functions Every function in the file (and other files with extern) Whole program run 0

#include <stdio.h>

int g = 10;              /* global — visible to all functions */

void show(void) {
    int g = 99;          /* local hides the global inside show() */
    printf("local g = %d\n", g);
}
Shadowing: a local with the same name as a global hides the global inside that block. Prefer distinct names to avoid confusion.
Topic 8

Scope and lifetime

  • Block scope — visible from declaration to the closing }
  • Function scope — parameters and locals of one function
  • File scope — globals; visible to every function below in the file
  • Program (whole-run) lifetime — globals and static variables
  • Automatic lifetime — created on entry to the block, destroyed on exit
Scope = WHERE the name is visible  •  Lifetime = HOW LONG the storage exists
These are independent: a static local has narrow scope (one block) but full program lifetime.
Topic 9

Types of storage classes

C has four storage class specifiers:

# Storage class Keyword Scope Lifetime Default value Stored in
1 Automatic auto Block (local) Until block ends Garbage Stack
2 Register register Block (local) Until block ends Garbage CPU register (hint) or stack
3 Static static Block (local) or file (internal linkage) Whole program 0 Data segment
4 External extern Whole program (all files) Whole program 0 Data segment

void demo(void) {
    auto int a = 1;      /* default for locals — keyword rarely written */
    register int r = 2;  /* hint only; &r is not allowed */
    static int s = 0;    /* initialized once; survives calls */
    s++;
    printf("a=%d r=%d s=%d\n", a, r, s);
}
Note: auto is the default for local variables, so you almost never type it. register is effectively obsolete — and you cannot apply & to a register variable.
Topic 10

static and extern in detail

static local — remembers its value across calls:


void counter(void) {
    static int count = 0;   /* runs only once */
    int plain = 0;             /* reset every call */
    count++; plain++;
    printf("count=%d plain=%d\n", count, plain);
}
/* Three calls print: 1 1 , 2 1 , 3 1 */

extern — refers to a variable defined in another file:


/* file1.c */
int total = 100;          /* definition */

/* file2.c */
extern int total;        /* declaration — no memory allocated here */
Where used static means extern means
Inside a function Keeps its value between calls (lifetime) Refers to a global defined elsewhere
At file scope (global) Private to this file (internal linkage) Declares a global from another file
On a function Callable only within this file Callable from other files (the default)
Topic 11

Storage classes and recursion

This is where the two topics meet. In a recursive function:

  • Automatic locals — each call has its own separate copy in its own frame
  • Static locals — one shared copy for all calls, so you can count invocations
  • Globals — also shared across all frames and all functions

void rec(int n) {
    static int calls = 0;  /* shared by every frame */
    int local = n;           /* fresh in every frame */

    calls++;
    if (n == 0) {
        printf("total calls = %d\n", calls);
        return;
    }
    rec(n - 1);
    printf("unwinding local = %d\n", local);
}
Notice that local still holds the right value after the recursive call returns — proof that every frame has its own automatic storage.

Level 2 Advanced Content

Going deeper into recursion

Level 1 covered what/why/applications, head vs tail, types, and recursion vs iteration. Level 2 explains the call stack in detail, converting head to tail with an accumulator, tail-call optimization, complexity, memoization, recursion on arrays and strings, memory segments, choosing a storage class, and common mistakes.

Topic 1

The call stack during recursion

Each call pushes a frame with its own parameters, locals, and return address. Frames stack up until the base case, then unwind in reverse (LIFO).

push fact(4) → push fact(3) → push fact(2) → push fact(1) = 1 → pop 2 → pop 6 → pop 24
  • Winding phase — calls go deeper; nothing is computed yet in head recursion
  • Base case — the deepest frame returns a concrete value
  • Unwinding phase — pending work in each frame completes
  • Depth × frame size ≈ stack used; exceeding the limit is a stack overflow
Topic 2

Converting head recursion to tail recursion

Carry the running result in an extra parameter (an accumulator) so nothing remains to do after the call.


/* Head style: multiply waits for the call to return */
long factHead(int n) {
    if (n <= 1) return 1;
    return n * factHead(n - 1);
}

/* Tail style: the call IS the return value */
long factTail(int n, long acc) {
    if (n <= 1) return acc;
    return factTail(n - 1, acc * n);
}
/* call as factTail(5, 1) */
The accumulator pattern turns recursion into a loop in disguise: state moves forward in the parameters instead of waiting in stack frames.
Topic 3

Tail-call optimization — what C guarantees

  • Standard C makes no promise about eliminating tail calls
  • GCC / Clang often do it with optimization enabled (-O2)
  • Without it, tail recursion still uses one frame per call
  • For guaranteed constant memory, write an explicit loop
Topic 4

Complexity — linear vs tree recursion

Function Calls per invocation Time Stack depth
fact(n) 1 O(n) O(n)
fib(n) naive 2 O(2^n) O(n)
fib(n) memoized 2 (cached) O(n) O(n)
Binary search 1 O(log n) O(log n)
Topic 5

Memoization — caching repeated subproblems


long memo[100];   /* zero-initialized globally */

long fib(int n) {
    if (n <= 1)
        return n;
    if (memo[n] != 0)
        return memo[n];      /* already computed */
    memo[n] = fib(n - 1) + fib(n - 2);
    return memo[n];
}
This drops naive Fibonacci from exponential to linear time — the single biggest win available in tree recursion.
Topic 6

Recursion on arrays and strings


/* Array sum: shrink the length */
int sum(int a[], int n) {
    if (n == 0)
        return 0;
    return a[n - 1] + sum(a, n - 1);
}

/* Palindrome: move both ends inward */
int isPal(char s[], int i, int j) {
    if (i >= j)
        return 1;
    if (s[i] != s[j])
        return 0;
    return isPal(s, i + 1, j - 1);
}
  • The array is not copied — only the pointer and index/length change
  • Pass indices when you need to work from both ends
  • Base cases: empty range (n == 0) or crossed indices (i >= j)
Topic 7

Memory segments — where each storage class lives

Segment Holds Lifetime
Stack Automatic locals, parameters, return addresses Per call / per block
Data (initialized) Globals and statics with non-zero initializers Whole program
BSS (uninitialized) Globals and statics left uninitialized — set to 0 Whole program
Heap Memory from malloc / calloc Until you free it
Registers Values the compiler chooses to keep in the CPU Very short — as needed
Deep recursion consumes the stack; static counters live in the data/BSS segment and cost nothing per call.
Topic 8

Choosing the right storage class

Need Use Why
A temporary value inside one function Plain local (automatic) Cheapest; cleaned up automatically
Remember a count between calls static local Narrow scope, program lifetime
Share data between functions in one file static global Shared but hidden from other files
Share data across multiple files Global + extern declaration One definition, many declarations
Micro-optimize a hot loop counter Nothing — trust the compiler register is obsolete in practice
Default to the narrowest scope that works. Globals make code harder to test and to reason about, especially alongside recursion where shared state changes across frames.
Topic 9

Common mistakes

Mistake Why it hurts Fix
No base case Infinite recursion → stack overflow Handle the smallest input first
Base case unreachable Argument never shrinks toward it Ensure each call reduces the problem
Base case forgets to return a value Caller uses an indeterminate result Return on every path
Calling fact(n) instead of fact(n-1) Same input repeats forever Decrement / divide the argument
Assuming tail recursion is free C does not guarantee optimization Use a loop when depth is large
Recomputing subproblems Exponential blow-up (naive Fibonacci) Memoize or go bottom-up
Expecting a plain local to persist between calls It is re-created (garbage) every call Declare it static
Re-initializing a static mentally each call The initializer runs only once Reset it explicitly if you need a fresh start
Defining a global in a header Multiple definitions at link time Define once in a .c, declare extern in the header
Taking & of a register variable Compile error — not addressable Drop the register keyword

Level 3 C Tricky Interview Points

Advanced tricky interview points on base cases, head vs tail recursion, stack depth, tail-call optimization, complexity, indirect recursion, and storage classes.

Tricky Interview Point-1:

Every recursion needs a base case AND progress toward it

A base case alone is not enough — each call must move closer to it. fact(n-1) shrinks n; fact(n) would recurse forever. Missing either condition gives infinite recursion and a stack overflow at runtime, not a compile error.

Tricky Interview Point-2:

Head recursion does work AFTER the call; tail recursion does it BEFORE

In head recursion the recursive call is the first action and the pending work happens while unwinding, so output appears in reverse order. In tail recursion the call is the last action and nothing remains to do after it returns.

Tricky Interview Point-3:

Tail calls are not guaranteed to be optimized in C

Standard C does not require tail-call optimization. GCC/Clang often do it at -O2, but you cannot rely on it for correctness. Deep tail recursion may still overflow the stack unless the compiler eliminates the frame.

Tricky Interview Point-4:

Each call gets its own copy of parameters and locals

Recursive calls do not share locals — every frame has separate storage. That is why n keeps its value after the inner call returns. A static local, by contrast, IS shared across all frames.

Tricky Interview Point-5:

Naive recursive Fibonacci is exponential — O(2^n)

fib(n) = fib(n-1) + fib(n-2) recomputes the same subproblems repeatedly. fib(40) makes over a billion calls. Fix with memoization or an iterative loop, which is O(n).

Tricky Interview Point-6:

Recursion depth is limited by stack size, not by any language rule

Typical stacks are 1–8 MB. With large frames you may overflow after only a few thousand calls; with tiny frames, tens of thousands. There is no portable maximum depth — it is environment-specific.

Tricky Interview Point-7:

Indirect (mutual) recursion also counts as recursion

If f() calls g() and g() calls f(), that is indirect recursion. It needs a base case just like direct recursion, and one of the two functions needs a forward prototype so the first can see the second.

Tricky Interview Point-8:

Any recursion can be rewritten iteratively (with an explicit stack if needed)

Linear recursion converts to a simple loop. Tree recursion may need your own stack or queue. This equivalence is why recursion is a convenience for expressing logic, not a unique capability.

Tricky Interview Point-9:

return without a value in a non-void recursive function is a trap

If the base case forgets to return a value, the caller multiplies by an indeterminate result. Make sure every path — base case and recursive case — returns.

Tricky Interview Point-10:

Tail recursion behaves like a loop; head recursion behaves like a stack

Tail recursion carries state forward in parameters (accumulator pattern). Head recursion defers work, so it naturally reverses order — perfect for printing a number's digits or a list backwards.

Tricky Interview Point-11:

Recursion on arrays usually passes an index or a shrinking length

sum(a, n) → a[n-1] + sum(a, n-1), or sum(a, i, n) advancing i. The array itself is not copied; only the pointer and index change per call.

Tricky Interview Point-12:

A recursive function calling itself in main-like fashion is still one definition

A function may call itself even though the definition is not finished being compiled, because its own declaration is already in scope from the header line. No extra prototype is needed for direct recursion.

Tricky Interview Point-13:

Storage class controls scope, lifetime, default value, and storage location

auto, register, static, and extern are the four storage class specifiers. Each answers four questions: where is the variable visible, how long does it live, what is its initial value if uninitialized, and where is it kept.

Tricky Interview Point-14:

A static local keeps its value between calls — including recursive calls

static int count = 0; inside a function is initialized once and shared by every invocation. In recursion this means all frames see the same counter, unlike ordinary locals which are separate per frame.

Tricky Interview Point-15:

Uninitialized static and global variables are zero; automatic ones are garbage

Static-duration objects live in a zero-initialized area. Automatic (stack) variables hold indeterminate values until assigned — reading them first is undefined behavior.

Tricky Interview Point-16:

static on a global means file-private, not "keeps its value"

At file scope static changes linkage (internal, invisible to other files), not lifetime — globals already live for the whole program. Inside a function static changes lifetime. Same keyword, two different jobs.

Tricky Interview Point-17:

register is only a hint, and you cannot take its address

&x is illegal for a register variable, and modern compilers allocate registers themselves, so the keyword is effectively obsolete. Mentioning the &-restriction is what interviewers look for.

Interview tip: define recursion by its two parts (base + recursive case), and classify head vs tail by whether any work remains after the recursive call.

Level 4 Simple Programs

Short programs covering factorial, head vs tail order, Fibonacci, GCD, array recursion, and storage classes (local / global / static).

Program 1 — Factorial: head style vs tail style


/* Same result; different frame behaviour */
#include <stdio.h>

long factHead(int n);
long factTail(int n, long acc);

int main(void) {
    int n;

    printf("Enter n: ");
    scanf("%d", &n);

    printf("Head: %ld\n", factHead(n));
    printf("Tail: %ld\n", factTail(n, 1));

    return 0;
}

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

long factTail(int n, long acc) {
    if (n <= 1)
        return acc;
    return factTail(n - 1, acc * n);
}

Program 2 — Head vs tail printing (order demo)


/* Moving printf flips the output order */
#include <stdio.h>

void headPrint(int n);
void tailPrint(int n);

int main(void) {
    printf("Head (1..5): ");
    headPrint(5);
    printf("\nTail (5..1): ");
    tailPrint(5);
    printf("\n");
    return 0;
}

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

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

Program 3 — Fibonacci and GCD


/* Tree recursion (fib) and tail recursion (gcd) */
#include <stdio.h>

long fib(int n);
int  gcd(int a, int b);

int main(void) {
    int n, x, y;

    printf("Enter n for Fibonacci: ");
    scanf("%d", &n);
    printf("fib(%d) = %ld\n", n, fib(n));

    printf("Enter two numbers for GCD: ");
    scanf("%d %d", &x, &y);
    printf("gcd = %d\n", gcd(x, y));

    return 0;
}

long fib(int n) {
    if (n <= 1)
        return n;                  /* fib(0)=0, fib(1)=1 */
    return fib(n - 1) + fib(n - 2);
}

int gcd(int a, int b) {
    if (b == 0)
        return a;
    return gcd(b, a % b);     /* tail recursive */
}

Program 4 — Array sum and string reverse by recursion


/* Recursion over arrays and strings */
#include <stdio.h>
#include <string.h>

int  sumArr(int a[], int n);
void reverse(char s[], int i, int j);

int main(void) {
    int a[] = {4, 8, 15, 16, 23};
    char s[] = "recursion";

    printf("Sum = %d\n", sumArr(a, 5));

    reverse(s, 0, (int)strlen(s) - 1);
    printf("Reversed = %s\n", s);

    return 0;
}

int sumArr(int a[], int n) {
    if (n == 0)
        return 0;
    return a[n - 1] + sumArr(a, n - 1);
}

void reverse(char s[], int i, int j) {
    char t;
    if (i >= j)
        return;
    t = s[i]; s[i] = s[j]; s[j] = t;
    reverse(s, i + 1, j - 1);
}

Program 5 — Storage classes: local, global, and static


/* Scope and lifetime in one program */
#include <stdio.h>

int gCount = 0;          /* global: whole program, default 0 */

void demo(void);

int main(void) {
    int i;
    for (i = 0; i < 3; i++)
        demo();

    printf("global gCount = %d\n", gCount);
    return 0;
}

void demo(void) {
    int plain = 0;          /* automatic: reset each call */
    static int kept = 0;   /* static: initialized once */

    plain++;
    kept++;
    gCount++;

    printf("plain=%d  kept=%d  gCount=%d\n", plain, kept, gCount);
}

/* Output:
   plain=1  kept=1  gCount=1
   plain=1  kept=2  gCount=2
   plain=1  kept=3  gCount=3
   global gCount = 3                */

Program 6 — Counting recursive calls with a static variable


/* A static local is shared by every recursive frame */
#include <stdio.h>

void rec(int n);

int main(void) {
    rec(4);
    return 0;
}

void rec(int n) {
    static int calls = 0;   /* one copy for all frames */
    int local = n;            /* fresh copy per frame */

    calls++;
    if (n == 0) {
        printf("base reached; total calls = %d\n", calls);
        return;
    }

    rec(n - 1);
    printf("unwinding: local=%d calls=%d\n", local, calls);
}
Comment Directive Type Keyword Function Variable String Number

Level 5 Problem Solving Tricks

How to master recursion while solving problems

Most recursion bugs come from a missing or unreachable base case, arguments that never shrink, and repeated subproblems. These habits make recursive solutions easy to write and easy to trust.

Trick 1

Write the base case first

Ask: what is the smallest input where the answer is obvious? factorial → n <= 1 gives 1; sum of array → n == 0 gives 0. Code that line before anything else.

Trick 2

Express the answer in terms of a smaller answer

State the recurrence in words: "factorial of n is n times factorial of n-1." If you cannot say that sentence, recursion is the wrong tool for the problem.

Trick 3

Check that every call shrinks the problem

The argument must strictly move toward the base case — n-1, n/2, i+1, or a shorter string. Passing n unchanged (or growing) means infinite recursion.

Trick 4

Trace two or three levels on paper

Write fact(3) → 3 * fact(2) → 3 * (2 * fact(1)) → 3 * 2 * 1. Seeing the unwind step makes head vs tail order obvious and exposes wrong base cases.

Trick 5

Use an accumulator to convert head into tail recursion

Instead of returning n * fact(n-1), carry the running result: factTail(n-1, acc * n). The recursive call becomes the final action, and nothing waits on the return.

Trick 6

Prefer iteration when depth can be large

Summing a million elements recursively risks stack overflow; a loop does not. Use recursion where the structure is naturally recursive (trees, divide and conquer) or depth is small.

Trick 7

Watch for repeated subproblems

If the same argument is computed more than once (classic Fibonacci), cache results in an array (memoization) or switch to a bottom-up loop. Otherwise the cost explodes.

Trick 8

Print inside the function to see the order

Add printf before the call and after it. Output before shows the descending path (tail-style order); output after shows the unwinding path (head-style, reversed).

Trick 9

Use a static local when you need to count calls

A plain local resets every call, so it can never total anything. static int calls = 0; is initialized once and shared by all frames — ideal for counting recursive invocations or cache hits.

Trick 10

Ask "scope or lifetime?" before reaching for static

Inside a function static extends lifetime. At file scope it restricts visibility. Naming which of the two you actually want prevents the most common storage-class mistake.

Quick success checklist

  • Before run: base case present? argument shrinks? every path returns a value?
  • Debug order: print the argument on entry → trace 3 levels on paper → check the unwind order
  • Design habit: state the recurrence in words → head or tail? → is depth safe, or use a loop?
Level-5 goal: write the base case first, guarantee progress toward it, and know whether your work happens before the call (tail) or after it (head).

Level 6 Practice Programs List

Programming questions only — focused on recursion (base cases, head vs tail style, linear and tree recursion, recursion over arrays and strings) and storage classes (local, global, static).

  1. 1. Write a C program that computes the factorial of n using recursion.
  2. 2. Write a C program that prints numbers from 1 to n using tail recursion, then from n down to 1 using head recursion.
  3. 3. Write a C program that computes the sum of the first n natural numbers recursively.
  4. 4. Write a C program that computes the nth Fibonacci number recursively and prints the number of calls made.
  5. 5. Write a C program that computes the GCD of two numbers using recursion (Euclid's algorithm).
  6. 6. Write a C program that reverses a string using recursion.
  7. 7. Write a C program that computes the sum of all elements in an array using recursion.
  8. 8. Write a C program that checks whether a string is a palindrome using recursion.
  9. 9. Write a C program with a function containing a static local counter that prints how many times the function has been called.
  10. 10. Write a C program that declares a global variable and a local variable with the same name, and prints both to demonstrate scope.
  11. 11. Write a C program that demonstrates the difference between an automatic local variable and a static local variable across three calls.
  12. 12. Write a C program with two functions sharing a global variable, where one modifies it and the other prints it.
  13. 13. Write a C program that uses a static counter inside a recursive function to count the total number of recursive calls made.