student@ubuntu:~$
c-foundations Lesson 5 9 min read

Functions & Recursion

Prototypes, pass-by-value, scope rules, and thinking recursively

Reading: Hanly & Koffman: §3.1–3.5 (pp. 108–145), §3.7 (pp. 163–168)

Quick check before you start: If you pass a variable to a function and the function changes it, does the original change? If you are unsure, read on. If you know the answer, skip to Recursion.

Practice this topic: C Functions skill drill

After this lesson, you will be able to:

  • Write function prototypes and definitions
  • Explain why C uses pass-by-value and what that means for callers
  • Identify variable scope (local vs. global)
  • Write recursive functions with proper base cases

Function Structure

Every C function has a return type, a name, and parameters. Unlike Java, there are no classes — functions live at file scope.

#include <stdio.h>

/* prototype — declares the function before main */
int add(int a, int b);

int main(void) {
    int result = add(3, 4);
    printf("%d\n", result);   /* 7 */
    return 0;
}

/* definition — implements the function */
int add(int a, int b) {
    return a + b;
}

The prototype tells the compiler the function exists, its return type, and its parameter types. Without it, calling add before its definition causes a compiler error or implicit declaration warning. Dr. Steiner requires explicit prototypes — always declare before main, define after.

Pass-by-Value

C passes arguments by value. The function gets a copy. Modifying the copy does not affect the original.

void try_to_change(int x) {
    x = 99;
    printf("Inside: %d\n", x);   /* 99 */
}

int main(void) {
    int num = 5;
    try_to_change(num);
    printf("Outside: %d\n", num); /* 5 — unchanged */
    return 0;
}

In Java, primitives worked the same way. But in Java, you could pass objects to modify their fields. C has no objects. To modify a caller’s variable, you need pointers (Week 6).

Key Insight: Every argument in C is copied into the function’s parameter. The function works on its own local copy. When the function returns, that copy is destroyed. This is pass-by-value — no exceptions.

Scope

Variables declared inside a function (or block) are local to that block. They are created when the block starts and destroyed when it ends.

int main(void) {
    int x = 10;

    if (x > 5) {
        int y = 20;       /* y exists only inside this if block */
        printf("%d\n", y);
    }
    /* printf("%d\n", y);  ERROR: y is not in scope */

    return 0;
}

Global variables (declared outside any function) are visible everywhere in the file. Avoid them — they make code harder to debug and test. Pass values through parameters instead.

int count = 0;             /* global — avoid this */

void increment(void) {
    count++;               /* modifies the global */
}

Recursion

A recursive function calls itself. Every recursive function needs a base case (when to stop) and a recursive case (when to call itself with a smaller problem).

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

The Call Stack

Each function call gets its own stack frame — a block of memory holding its parameters and local variables. Recursive calls stack up:

factorial(4) → waits for factorial(3)
  factorial(3) → waits for factorial(2)
    factorial(2) → waits for factorial(1)
      factorial(1) → returns 1
    factorial(2) → returns 2 * 1 = 2
  factorial(3) → returns 3 * 2 = 6
factorial(4) → returns 4 * 6 = 24

If there is no base case (or the base case is never reached), the stack grows until it overflows — a stack overflow crash. The stack is finite, typically 1–8 MB.

/* Fibonacci — correct but slow (exponential time) */
int fib(int n) {
    if (n <= 1) {
        return n;
    }
    return fib(n - 1) + fib(n - 2);
}

Check Your Understanding
What does the following code print?</p>
void double_it(int x) {
    x = x * 2;
}
int main(void) {
    int val = 5;
    double_it(val);
    printf("%d\n", val);
}
A10
B5
C0
DUndefined behavior
Answer: B. C is pass-by-value. When double_it(val) is called, the value 5 is copied into the parameter x. The function doubles its local copy to 10, but val in main is untouched. It still holds 5. To actually modify the caller's variable, you would need to pass a pointer (covered in Week 6).

What Comes Next

Functions let you organize code, but you still cannot handle collections of data. Next, you will learn arrays and strings — C’s way of storing sequences.