Functions & Recursion

Functions are how C programs stay organized past a few dozen lines — a named, reusable block of logic that takes inputs and (optionally) hands back a result, including the special case where a function calls itself.

A function packages a piece of logic under one name, so a program can call that name instead of retyping the same statements every time they're needed. Nearly every C program already relies on this idea, even if the only function in sight is main. Recursion pushes it one step further: a function is allowed to call itself, as long as each call works on a smaller version of the original problem and there's a clear point where the calls stop. This page walks from the plain anatomy of a function through how values move in and out of one, all the way to recursive functions like factorial and Fibonacci, one idea at a time.

What Is a Function?

A function is simply a named block of statements that a program can run by referring to that name, instead of writing the same statements out every time they're needed.

greet is the function's name. The void that comes before it means the function doesn't hand back any value; the void inside the parentheses means it doesn't take any inputs. Everything between the curly braces is the function's body — the code that actually runs. main triggers all of that with a single call: greet();.

#include <stdio.h>

void greet(void) {
    printf("Hello\n");
}

int main(void) {
    greet();
    return 0;
}

Why Functions Are Needed

Once a program grows past a handful of lines, functions start paying for themselves in several ways at once: they let you break a big problem into smaller pieces (modularity), write a calculation once and reuse it anywhere it's needed instead of copy-pasting it (reusability), give a chunk of logic a name that says what it does (readability), let you fix or improve that logic in exactly one place (maintainability), and let you test one small piece of behavior on its own instead of the whole program at once.

Even without seeing what's inside these three functions, their names alone say what the program is doing at this point — that's abstraction: hiding the details behind a name so a reader can follow the big picture first and drill into specifics only when they need to.

calculate_speed();
read_sensor();
display_result();

Basic Function Structure

Every function follows the same general shape: a return type, a name, a parameter list in parentheses, and a body in curly braces that ends by (optionally) returning a value.

Reading it piece by piece: int is the return type — the kind of value this function hands back; add is the function's name; (int a, int b) declares two parameters named a and b that the function expects to receive; and the body computes result and returns it.

int add(int a, int b) {
    int result = a + b;
    return result;
}

Function Declaration (the Prototype)

A function declaration — often called a prototype — tells the compiler a function's name, its return type, and the number and types of its parameters, without providing any implementation.

This single line is enough for the compiler to correctly check any call to add elsewhere in the file, even before it has seen how add actually works.

int add(int a, int b);

Function Definition

A function definition is the real thing: the declaration plus a body that actually implements the behavior.

A declaration just makes a promise about the function's signature; a definition keeps that promise by supplying the statements that run when the function is called.

int add(int a, int b) {
    return a + b;
}

Making the Call

Calling a function means writing its name followed by parentheses containing the arguments it needs; C runs the function's body and, if it returns a value, that value can be used right where the call was written.

main hands the values 10 and 20 to add.

Inside add, those values are added together to produce 30.

add returns 30, which replaces the call add(10, 20) in the expression it appeared in.

result is assigned 30, and execution continues with the next statement in main.

int result = add(10, 20);

Putting It Together: A Complete Example

Declaration, call, and definition all show up together in a real program — the prototype lets the compiler check the call in main before it has even reached the real definition further down the file.

Notice the prototype above main, the call inside main, and the actual definition below main — running this program prints Result = 30.

#include <stdio.h>

int add(int a, int b);

int main(void) {
    int result = add(10, 20);
    printf("Result = %d\n", result);
    return 0;
}

int add(int a, int b) {
    return a + b;
}

Function Parameters

Parameters are the inputs a function is written to accept; a function can declare as many as it needs.

Calling multiply(5, 4) sends 5 into x and 4 into y for that one call — a different call can supply completely different values for the same parameters.

int multiply(int x, int y) {
    return x * y;
}

Parameters vs Arguments

These two words are often used interchangeably in casual conversation, but they refer to different things. Parameters are the variables listed in a function's own declaration — in int add(int a, int b), a and b are parameters. Arguments are the actual values supplied at a particular call site — in add(10, 20), 10 and 20 are arguments. A parameter is a placeholder that belongs to the function; an argument is the real value that fills that placeholder for one specific call.

Functions with No Parameters

Prefer the prototype void display(void); over void display();. Writing void inside the parentheses explicitly tells the compiler this function accepts zero arguments, so a mistaken call like display(1, 2) gets caught as an error instead of silently compiling.

void display(void) {
    printf("Hello\n");
}

Functions with No Return Value

A function declared void doesn't hand back a result at all — it's called purely for the side effect it produces, such as printing something to the screen.

void print_message(void) {
    printf("Hello\n");
}

Functions with a Return Value

square(5) evaluates to 25, and that returned value can be used just like any other value of its type: assigned to a variable, embedded inside a larger expression, passed straight into another function call, or tested inside a condition.

Here the returned value is used directly inside a condition, with no intermediate variable needed at all.

int square(int x) {
    return x * x;
}
if (square(5) > 20) {
    printf("Big enough\n");
}

The return Statement

return immediately ends the function it's inside. In a function with a non-void return type it also hands back a value to the caller; in a void function it can be written with no value at all, purely to exit early.

A bare return like this is common as an early-exit guard: if a condition means there's nothing left to do, the function leaves immediately instead of falling through the rest of its body.

int get_number(void) {
    return 100;
}
if (x < 0) {
    return;
}

Multiple return Statements

A function is free to have more than one return statement, each covering a different case; whichever one actually executes for a given call ends the function right there. absolute_value(-10) takes the first return and gives 10; absolute_value(10) skips it and takes the second, also giving 10.

int absolute_value(int x) {
    if (x < 0) {
        return -x;
    }
    return x;
}

Pass-by-Value

C passes arguments by value: a function receives its own private copy of whatever was passed in, not the caller's original variable.

This prints 10, not 100. change receives its own copy of a's value inside its parameter x. Modifying x only modifies that copy — it has no way to reach back and change a itself.

#include <stdio.h>

void change(int x) {
    x = 100;
}

int main(void) {
    int a = 10;
    change(a);
    printf("%d\n", a);
    return 0;
}

Why This Matters

This still prints 5. increment only changes its own local copy of value, so the caller's variable is exactly as it was before the call.

Actually changing a caller's variable from inside a function needs a different mechanism — passing a pointer to that variable — which is a later topic. For now, remember that a plain parameter is always a copy.

void increment(int x) {
    x++;
}

int main(void) {
    int value = 5;
    increment(value);
    printf("%d\n", value);
    return 0;
}

Local Variables and Scope

Variables declared inside a function body exist only for that function's own use — this is called block scope. The variable result declared inside add() has no meaning outside of add(); main() cannot see or touch it directly. That isolation is a feature: it keeps a function's internal bookkeeping private, so two different functions can freely reuse a name like result or i without stepping on each other.

Prototype Parameter Names Are Optional

A prototype only needs to describe the types the compiler should expect, not the names those values will eventually get inside the function body — so int add(int a, int b); and int add(int, int); declare exactly the same function.

Both compile identically, but keeping descriptive names in the prototype makes it far easier for a human reader to understand, even though the compiler itself doesn't need them there.

int calculate_area(int length, int width); // reads clearly
int calculate_area(int, int);              // compiles the same way

Defining a Function After main

Because the prototype above main already told the compiler everything it needs about square's signature, main is free to call square before the compiler has even reached its real definition further down the file.

#include <stdio.h>

int square(int n);

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

int square(int n) {
    return n * n;
}

Defining a Function Before main

Here the complete definition appears before its first use, so no separate prototype is required at all — by the time main calls square, the compiler has already seen everything about it.

#include <stdio.h>

int square(int n) {
    return n * n;
}

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

main() Is a Function Too

main follows exactly the same rules as any other function: it has a return type (int), a name (main), a parameter list, and a body. The two standard forms are int main(void), for a program that ignores any command-line input, and int main(int argc, char *argv[]), for a program that reads arguments typed on the command line.

Functions Calling Other Functions

main calls calculate(5); calculate itself calls square(5), gets back 25, adds 10, and returns 35 to main. Functions calling other functions — sometimes several layers deep — is completely ordinary in C.

#include <stdio.h>

int square(int n) {
    return n * n;
}

int calculate(int n) {
    return square(n) + 10;
}

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

The Function Call Stack

Every time a function calls another, the runtime needs to remember where to resume once the called function finishes; it keeps track of this with a call stack, where the most recently called function always finishes and returns before the one that called it.

This nested, last-in-first-out ordering is exactly what makes recursion possible: a function calling itself just adds another layer onto this same stack.

+---------------+
|  square()     |  <- called last, finishes first
+---------------+
|  calculate()  |
+---------------+
|  main()       |  <- called first, finishes last
+---------------+

What Is Recursion?

Recursion is what happens when a function calls itself, working each time on a smaller piece of the original problem.

count_down calls itself, each time with a smaller number, until n reaches 0. Calling count_down(3) prints 3, then 2, then 1, then stops.

#include <stdio.h>

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

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

Two Essential Parts of Recursion

Every correct recursive function needs two things: a base case, the condition that stops the recursion (if (n == 0) { return; } above), and a recursive case, where the function calls itself with a problem that's measurably smaller or simpler than the one it was given (count_down(n - 1);).

Leave out a solid base case, or write one the recursion never actually reaches, and the calls keep spawning more calls indefinitely — until the program runs out of resources and crashes.

Tracing a Recursive Call

The calls first cascade all the way down to the base case, and only then does each one finish and return, working its way back up in the reverse order the calls were made.

count_down(3)
  prints 3, calls count_down(2)
    prints 2, calls count_down(1)
      prints 1, calls count_down(0)
        n == 0, returns immediately
      returns
    returns
  returns

Recursion and the Call Stack

Each active call keeps its own private n and its own place to resume, layered on the stack exactly like any other nested function call. When count_down(0) returns, its layer is removed and count_down(1) picks up right where it left off.

Recursing very deeply — thousands of nested calls — can use enough stack space to crash the program, which is one reason a solid, quickly-reached base case matters so much.

+------------------+
| count_down(0)    |  <- finishes first
+------------------+
| count_down(1)    |
+------------------+
| count_down(2)    |
+------------------+
| count_down(3)    |
+------------------+
| main()           |  <- finishes last
+------------------+

Factorial Using Recursion

The factorial of n, written n!, is the product of every whole number from n down to 1 — for example 5! = 5 x 4 x 3 x 2 x 1 = 120. It also has a recursive definition: n! equals n times (n-1)!, with the base case 0! = 1.

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

Tracing factorial(4)

The calls expand downward until factorial(0) is reached, which returns 1 immediately without recursing further.

factorial(1) then computes 1 * 1 = 1 and returns.

factorial(2) computes 2 * 1 = 2 and returns.

factorial(3) computes 3 * 2 = 6 and returns.

factorial(4) computes 4 * 6 = 24 and returns that final answer.

factorial(4)
= 4 * factorial(3)
= 4 * (3 * factorial(2))
= 4 * (3 * (2 * factorial(1)))
= 4 * (3 * (2 * (1 * factorial(0))))
= 4 * (3 * (2 * (1 * 1)))
= 4 * (3 * (2 * 1))
= 4 * (3 * 2)
= 4 * 6
= 24

Recursion Must Make Progress

Before trusting any recursive function, ask: does every call move strictly closer to the base case? If the answer isn't clearly yes, the recursion can run forever — or, in practice, until the call stack overflows.

return n * factorial(n - 1); // n shrinks toward 0 -- fine
return n * factorial(n);     // n never changes -- the base case is never reached

Recursive Fibonacci

The Fibonacci sequence is 0, 1, 1, 2, 3, 5, 8, 13, and so on, where each number is the sum of the two before it. Defined recursively: F(0) = 0, F(1) = 1, and F(n) = F(n-1) + F(n-2) for anything larger.

This has two base cases instead of one, and its recursive case makes two separate recursive calls rather than just one.

int fibonacci(int n) {
    if (n == 0) {
        return 0;
    }
    if (n == 1) {
        return 1;
    }
    return fibonacci(n - 1) + fibonacci(n - 2);
}

The Fibonacci Recursion Tree

Expanding fibonacci(4) branches into a tree of calls, and fibonacci(2) and fibonacci(1) each get recomputed from scratch more than once. The recursive version is short and reads exactly like the mathematical definition, but that repeated work makes it expensive for larger n — recursion trades code simplicity for performance in some cases, not automatically both at once.

fibonacci(4)
 +-- fibonacci(3)
 |    +-- fibonacci(2)
 |    |    +-- fibonacci(1)
 |    |    +-- fibonacci(0)
 |    +-- fibonacci(1)
 +-- fibonacci(2)
      +-- fibonacci(1)
      +-- fibonacci(0)

Recursion vs Iteration: Same Problem, Two Ways

This produces exactly the same answers as the recursive factorial from earlier, just by repeating a loop body instead of having the function call itself.

int factorial_iterative(int n) {
    int result = 1;
    for (int i = 1; i <= n; i++) {
        result *= i;
    }
    return result;
}

Recursion vs Loop, Side by Side

Neither approach is universally better — a loop is usually the simpler, cheaper choice for straightforward repeated counting, while recursion tends to read more naturally once a problem is already defined in terms of smaller versions of itself.

Repetition mechanism    recursion: function calls itself        loop: body repeats
Termination             recursion: base case                    loop: loop condition
Uses the call stack     recursion: yes, one frame per call      loop: no extra frames
Best fit                recursion: tree-shaped, self-similar    loop: plain counting / repetition
Stack-overflow risk     recursion: higher on deep recursion     loop: essentially none

When Recursion Is the Right Tool

Recursion tends to shine on problems that are naturally self-similar: walking a tree or a nested directory structure, divide-and-conquer algorithms that split a problem in half at each step, mathematical definitions that are already stated recursively, backtracking search, and certain sorting and searching algorithms. It isn't automatically the right choice for every repeated task — a plain loop is often simpler and cheaper for anything that's just "do this n times".

Common Recursion Mistakes

Missing a base case entirely — a function that only ever calls itself with no condition that ever stops it recurses until the program fails.

Writing a base case that's technically present but never actually reached — for example moving away from it (incrementing when the recursion needed to decrement), so the stopping condition is never satisfied.

Recursing so deeply that the call stack itself runs out of space — a stack overflow — which can happen even when the recursive logic is otherwise completely correct, if the input is simply large enough.

Common Function Mistakes

Calling a function the compiler hasn't seen declared or defined yet — with no prototype above it and no earlier definition, the compiler has no way to know its signature.

Returning the wrong thing on some path — for instance a non-void function where one branch reaches a bare return, leaving that path with nothing meaningful to give back.

Ignoring a return value that the caller actually needed — calling a function purely for its side effect when its whole point was the value it hands back.

Writing one oversized function that tries to do everything (a do_everything(void) with no clear single job) instead of splitting the work into several smaller, focused functions.

Function Design Principles

A well-designed function tends to share four traits: a clear purpose that its name actually communicates (calculate_average is far more informative than a vague process_data), a clear interface where the parameters and return type make it obvious what goes in and what comes out, limited responsibility — one coherent job rather than several unrelated ones bundled together — and predictable behavior, meaning the same inputs consistently produce the same kind of result every time it's called.

Complete Example: Three Focused Functions

Each calculation lives in its own small function instead of being written inline three separate times inside main — exactly the modularity and reuse this whole page started with.

#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int multiply(int a, int b) {
    return a * b;
}

int main(void) {
    int x = 10, y = 4;
    printf("Sum = %d\n", add(x, y));
    printf("Difference = %d\n", subtract(x, y));
    printf("Product = %d\n", multiply(x, y));
    return 0;
}

Complete Example: Factorial by Recursion

Running this program prints Factorial = 120 — the recursive definition from earlier on this page, now assembled into a complete, runnable program.

A function is a named block of code that performs a task, optionally accepts parameters (inputs), and optionally returns a value. Recursion is when a function calls itself, usually to break a problem into a smaller version of the same problem.

Without functions, every program would be one giant, unstructured block — functions let you write logic once and reuse it anywhere, name a piece of behavior clearly, and reason about a program one small piece at a time instead of all at once.

Absolutely everywhere in real code — even 'Hello, World' uses one function (main) and calls another (printf). Recursion specifically shows up in tree/graph traversal, parsing, and mathematical definitions like factorial or Fibonacci that are naturally self-referential.

A function is like a recipe card: 'Sauce (needs: tomatoes, salt) → chop tomatoes, cook with salt, return sauce.' You can hand that card to anyone and they get the same result without you re-explaining it each time. Recursion is a recipe that says 'to make a sauce for N people, make sauce for N-1 people, then add one more portion' — eventually bottoming out at a recipe for 1 person that doesn't refer to itself anymore.

A plain (non-recursive) function first The simplest possible function: it takes two parameters, computes a value, and returns it — nothing recursive yet.

add(3, 4) is called; C copies the values 3 and 4 into add's own private parameters a and b.

Inside add, a + b computes 7, and return sends that value back to wherever add was called from.

Back in main, result is assigned the returned value 7, which the final printf displays.

The same idea, but the function calls itself factorial calls itself with a smaller argument each time, until it reaches the base case that stops the recursion.

main calls factorial(5). Since 5 > 1, it doesn't hit the base case, so it computes 5 * factorial(4) — but first it must find out what factorial(4) is.

factorial(4) similarly needs factorial(3), which needs factorial(2), which needs factorial(1).

factorial(1) finally hits the base case (n <= 1) and returns 1 directly, without calling itself again — this is what stops the recursion from running forever.

Now the calls unwind in reverse: factorial(2) computes 2 * 1 = 2, factorial(3) computes 3 * 2 = 6, factorial(4) computes 4 * 6 = 24.

Finally factorial(5) computes 5 * 24 = 120, which is what gets returned to main and printed.

void addOne(int x) { x = x + 1; } int n = 5; addOne(n); // expecting n to become 6 void addOne(int *x) { *x = *x + 1; } int n = 5; addOne(&n); // now n really becomes 6

Parameters are passed by value — x inside addOne is a private copy of n, so modifying x has zero effect on the caller's n. Passing a pointer to n lets the function reach into the caller's actual memory instead.

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

Without a base case, factorial keeps calling itself with smaller and smaller (eventually negative) n forever, never stopping — this exhausts the call stack and crashes with a stack overflow.

Every recursive function needs a base case (a condition that stops the recursion) and a recursive case that moves measurably closer to that base case — without both, you get infinite recursion, which crashes with a stack overflow.

Parameters in C are passed by value by default — the function receives a copy of the argument, so modifying a parameter inside a function does not change the caller's original variable (achieving that requires passing a pointer, covered in the Pointers topics).

A function that returns void returns no value at all; calling it is a statement, not an expression that produces something usable.

Each function call gets its own fresh set of local variables and parameters, stored in a new stack frame — this is exactly why recursive calls to the same function don't overwrite each other's variables.

Writing a recursive function with no base case, or a base case that's never actually reachable from the recursive case — both cause infinite recursion and a stack overflow crash.

Assuming a function can modify a variable passed to it by value — void addOne(int x) { x++; } has zero effect on the caller's variable, since x is a private copy inside addOne.

Forgetting a return statement in a non-void function on some code path — the function then returns an unpredictable, indeterminate value on that path, a bug the compiler will often (but not always) warn about.

Writing genuinely deep or unbounded recursion (like recursing once per element of a huge array) where an iterative loop would use far less memory — each recursive call consumes real stack space.

Reaching for recursion naturally when a problem is already defined recursively (tree traversal, computing Fibonacci, quicksort/mergesort) — forcing recursion onto a problem that's naturally iterative (like summing an array) usually just adds overhead.

Always writing and testing the base case first when designing a recursive function, then building the recursive case around it.

Declaring small, single-purpose helper functions rather than one enormous main() — it makes testing, debugging and reading the program dramatically easier.

The call stack, stack frames, and stack overflow Every function call pushes a new stack frame containing its parameters, local variables, and a return address onto the program's call stack. Recursive functions without a reachable base case keep pushing frames until the stack's fixed memory region is exhausted, crashing with a stack overflow — a concept developed fully with diagrams in the future Memory & Program Execution track.

Tail call optimization (and why C mostly doesn't do it) A 'tail call' is a recursive call that is the very last operation in a function, with nothing left to do after it returns. Some languages automatically convert tail calls into a simple loop internally, using no extra stack space no matter how deep the recursion — the C standard does not require this, and most C compilers do not perform it reliably, meaning deeply tail-recursive C code can still overflow the stack unless manually rewritten as a loop.

What two things does every correct recursive function need?

A base case that stops the recursion, and a recursive case that makes measurable progress toward that base case with every call.

Are function arguments passed by value or by reference in C?

By value, always — a function receives a copy of each argument. To let a function modify the caller's original variable, you must explicitly pass a pointer to it.

What causes a stack overflow in a recursive function?

Recursing so deeply (or infinitely, due to a missing/unreachable base case) that the call stack's fixed memory region is exhausted by the accumulated stack frames.

What's the difference between a parameter and an argument?

A parameter is the variable named in a function's own declaration (the a and b in int add(int a, int b)); an argument is the actual value supplied at a particular call site (the 10 and 20 in add(10, 20)).

Why doesn't a function that receives a plain int parameter ever change the caller's original variable?

C passes arguments by value, so the function receives its own private copy of the value. Modifying the parameter only modifies that copy, never the variable back in the caller.

What are the two things every correctly written recursive function must have?

A base case that stops the recursion, and a recursive case in which the function calls itself with a problem that is measurably smaller or simpler, so it actually progresses toward the base case.

If a function is defined below main, how can main still call it without errors?

By putting a matching prototype (declaration) above main. The prototype tells the compiler the function's name, return type, and parameter types in advance, so it can check the call before reaching the real definition further down the file.

Why can recursive Fibonacci become slow for larger n even though the code itself is short?

Because the same smaller Fibonacci values get recomputed from scratch many times across the branching tree of calls, so the amount of work grows very quickly with n even though each individual call does very little.

Write a recursive C function that computes the factorial of a non-negative integer, and call it from main to print the factorial of 5.

The base case 0! = 1 stops the recursion; every other call multiplies n by the factorial of n - 1, so the calls unwind back up multiplying as they return, giving 5! = 120.

Write a recursive C function that returns the nth Fibonacci number, and print the 8th value from main.

Two base cases stop the recursion at 0 and 1; every other call splits into two smaller recursive calls whose results are added together, matching the definition F(n) = F(n-1) + F(n-2).

Write a recursive C function that computes the absolute value of an integer without using the standard library's abs function.

A negative input recurses exactly once with its sign flipped, landing on the base case x >= 0, which returns the value unchanged; that's why both -15 and 15 come back as 15.

Write a recursive C function that counts down from a given positive number to 1, and also write an iterative version of the same task using a loop, calling both from main.

Both versions print the same 3, 2, 1 sequence; the recursive version replaces the loop's repeated body with a function that keeps calling itself until n reaches the base case 0, while the iterative version just decrements a loop counter.

Write a C program with a swap function that takes two plain int parameters and tries to swap them, then show from main that the caller's variables are unaffected.

swap only exchanges its own local copies a and b; because C passes arguments by value, x and y in main are never touched, so both printf calls show the same values — genuinely swapping the caller's variables needs pointers, which come later.

Write a recursive C function that prints the characters of a string in reverse order.

Each call prints one character starting from the last index, then recurses on index - 1; the base case index < 0 stops it once every character has been printed, producing the string in reverse order.

Write a recursive C function that checks whether a string is a palindrome (reads the same forwards and backwards).

The function compares the outermost pair of characters and recurses inward on the remaining substring; a mismatched pair returns 0 immediately, and if the two indices meet or cross without ever mismatching, the base case returns 1.

Write a recursive C function that computes the greatest common divisor (GCD) of two integers.

This is Euclid's algorithm: the base case b == 0 returns a as the answer, and each recursive step replaces (a, b) with (b, a % b), a pair that's always smaller yet shares the same greatest common divisor as the original.

Write a menu-driven C program that uses three separate functions — add, subtract, and multiply — so main only needs to read a menu choice and call the matching one.

Each arithmetic operation lives in its own small, focused function; main's only job is to read the menu choice and call the one function that matches it, instead of cramming all three calculations directly inline.

Write a recursive C function that returns the sum of the digits of a non-negative integer.

n % 10 peels off the last digit and n / 10 drops it, so each recursive call works on a shorter number; the base case n == 0 stops it once every digit has been added in, giving 1+2+3+4 = 10.

#include <stdio.h>

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

int main(void) {
    int number = 5;
    printf("Factorial = %d\n", factorial(number));
    return 0;
}
#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int main() {
    int result = add(3, 4);
    printf("%d\n", result);
    return 0;
}
#include <stdio.h>

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

int main() {
    printf("%d\n", factorial(5));
    return 0;
}