🧱 Section 1 · Foundations 🟡 Beginner–Intermediate MODULE 04

Functions — Definition, Overloading, Recursion

⏱️ 27 min read
📖 Theory + Code
🧩 5 Quiz Questions
🏗️ 1 Challenge
Your progress in Section 180%
🎯 What you'll learn: How to declare and define functions with typed parameters and return values, the crucial difference between passing arguments by value and by reference, default parameter values, function overloading (a C++ feature Python doesn't have), and recursion — with a base case that keeps it from running forever.

Function Declaration, Definition & Return Types

A C++ function has a return type (what kind of value it hands back), a name, a list of typed parameters, and a body. If a function doesn't return anything, its return type is void.

add.cpp — A Basic Function
C++
// add.cpp
#include <iostream>

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

int main() {
    int result = add(3, 4);
    std::cout << "3 + 4 = " << result << std::endl;
    return 0;
}
📝
Every part is typed
int add(int a, int b) — the return type (int) comes first, then the name (add), then each parameter with its own type (int a, int b). C++ checks all of this at compile time: calling add("hi", "there") would fail to compile, because strings aren't ints.

Pass by Value vs. Pass by Reference

By default, C++ passes arguments by value — the function receives a copy of the argument, so changes inside the function don't affect the caller's original variable. Adding an ampersand & to a parameter's type makes it a reference parameter — the function then operates on the caller's actual variable, and changes stick.

pass_demo.cpp — Value vs. Reference Parameters
C++
// pass_demo.cpp
#include <iostream>

void addOneByValue(int n) {
    n = n + 1; // only changes the local copy
}

void addOneByReference(int& n) {
    n = n + 1; // changes the caller's actual variable
}

int main() {
    int x = 10;

    addOneByValue(x);
    std::cout << "After addOneByValue: " << x << std::endl;     // still 10

    addOneByReference(x);
    std::cout << "After addOneByReference: " << x << std::endl; // now 11

    return 0;
}
A preview of pointers
References let a function reach back and modify the caller's variable without copying large data structures. This same idea — letting a function operate on the original data instead of a copy — is the whole motivation behind pointers, which you'll meet properly in Lesson 8. References are, in a sense, a safer, easier-to-use cousin of pointers.
⚠️
Why pass by reference at all?
Beyond letting a function modify the caller's variable, pass-by-reference avoids copying large objects (like a big std::string or a container) every time you call a function — which matters for performance. You'll often see const std::string& as a parameter type: a reference for efficiency, with const to promise the function won't modify it.

Default Parameter Values

A parameter can have a default value, used automatically when the caller doesn't supply one. This lets you offer a simpler call signature for the common case while still allowing full control when needed.

price.cpp — A Default Tax Rate
C++
// price.cpp
#include <iostream>

double calculatePrice(double base, double taxRate = 0.05) {
    return base + (base * taxRate);
}

int main() {
    std::cout << calculatePrice(100) << std::endl;         // uses default 0.05 -> 105
    std::cout << calculatePrice(100, 0.10) << std::endl;   // overrides default -> 110
    return 0;
}
⚠️
Default parameters must come last
Once a parameter has a default value, every parameter after it must also have one. double calculatePrice(double taxRate = 0.05, double base) would not compile — base has no default and comes after one that does.

Function Overloading

C++ lets you define multiple functions with the same name, as long as their parameter lists differ (in number or type of parameters). The compiler picks the right one based on the arguments you pass. This is called overloading — and it's a feature Python doesn't have, since Python resolves function names dynamically at call time rather than matching parameter types at compile time.

overload.cpp — Same Name, Different Parameters
C++
// overload.cpp
#include <iostream>

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

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

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

int main() {
    std::cout << multiply(2, 3) << std::endl;         // calls the (int,int) version -> 6
    std::cout << multiply(2.5, 4.0) << std::endl;      // calls the (double,double) version -> 10
    std::cout << multiply(2, 3, 4) << std::endl;      // calls the (int,int,int) version -> 24
    return 0;
}
💡
Overload resolution happens at compile time
The compiler looks at the number and types of arguments in each call and matches it to exactly one of the overloaded functions before the program ever runs. Return type alone is not enough to distinguish overloads — two functions with the same name and same parameters but different return types won't compile.

Recursion

A recursive function calls itself to solve a smaller version of the same problem. Every correct recursive function needs a base case — a condition where it stops calling itself and returns directly — otherwise it recurses forever (and eventually crashes with a stack overflow).

factorial.cpp — Recursive Factorial
C++
// factorial.cpp
#include <iostream>

int factorial(int n) {
    if (n <= 1) {          // base case: stop recursing
        return 1;
    }
    return n * factorial(n - 1); // recursive case: smaller subproblem
}

int main() {
    std::cout << "5! = " << factorial(5) << std::endl; // 120
    return 0;
}
1
factorial(5) calls factorial(4)
5 isn't ≤ 1, so it returns 5 * factorial(4) — but first it needs the result of factorial(4).
2
...which calls factorial(3), then factorial(2), then factorial(1)
Each call is waiting on the next, building up a chain: 5 * (4 * (3 * (2 * factorial(1)))).
3
factorial(1) hits the base case and returns 1
No more recursive calls happen. This is the moment the chain stops growing and starts resolving.
4
The chain unwinds back to the top
2*1=2, 3*2=6, 4*6=24, 5*24=120. Each waiting call finally gets its answer and returns it up the chain.
🐛
Forgetting the base case is a real bug, not a style choice
If factorial never checked n <= 1, it would call itself with smaller and smaller n forever — through 0, then negative numbers with no end — consuming stack memory on every call until the program crashes with a stack overflow.

Lesson Summary

Let's recap everything you learned in this lesson:

A function's signature is its return type, name, and typed parameter list — all checked at compile time.
Pass by value copies the argument; pass by reference (&) lets the function modify the caller's original variable.
Default parameters supply a value when the caller doesn't — and must be the trailing parameters in the list.
Overloading lets multiple functions share a name if their parameter lists differ — resolved at compile time, not something Python offers.
Recursion needs a base case to stop — without one, the function calls itself forever and crashes.
🧩 Knowledge Check — Lesson 4
Answer all 5 questions to test your understanding. Instant feedback on every answer.
1. What does a function's return type specify?
2. What's the key difference between pass by value and pass by reference?
3. What is function overloading?
4. In recursion, what is a "base case"?
5. Which symbol turns a parameter into a reference parameter?
💪
Coding Challenge — Lesson 4
Apply what you learned · Beginner–Intermediate Level

Now it's your turn to write real code. Compile and run it with g++ or an online compiler.

Challenge: Sum of Digits 🔢

Write a program called sum_digits.cpp with a function int sumDigits(int n) that adds up the individual digits of a number. You can implement it either recursively (using n % 10 and n / 10, with a base case for n == 0) or iteratively with a loop. Call it from main() and print the result for 12345:

Sum of digits of 12345 = 15

Rules: The function must take an int and return an int. Print the result using std::cout in main().
💡 Show hints if you're stuck
  • Recursive base case: if (n == 0) return 0;
  • Recursive step: return (n % 10) + sumDigits(n / 10);
  • Iterative version: loop while (n > 0), add n % 10 to a running total, then do n /= 10;
  • 12345 → 1 + 2 + 3 + 4 + 5 = 15
Finished this lesson?
Mark it complete to track your progress.
🎉

Lesson 4 Complete!

You can now organize code into reusable, well-typed functions. One more stop before Section 2 — the C++ Foundations Quiz.

Module 04 of 26 Section 1 — C++ Foundations