Defining and calling functions in C++, including parameters, return values, and breaking a program into smaller, reusable pieces.
Introduction
A function is a named block of code that performs one clear task. Instead of writing the same steps every time we need them, we write those steps once, give them a name, and call the function whenever necessary.
We already use functions in C++ even before studying them directly. Every program has a main function, and expressions such as min(a, b) and sqrt(x) call functions from the standard library. Now we will learn how to create our own functions.
Functions are especially useful in competitive programming. They let us separate the solution into small, manageable parts: one function can check whether a number is prime, while main reads the input, calls the helper function, and prints the answer. This makes the code easier to understand, test, debug, and reuse.
Problem or motivation
Suppose we are given q integers. For every integer x, we must print YES if x is prime and NO otherwise.
A prime number is an integer greater than 1 that has exactly two positive divisors: 1 and itself. To test one number, we can try possible divisors while . If one of them divides , the number is not prime.
d
d2≤x
x
The prime check is one independent job. The input loop in main should not need to know every detail of that job. It should be able to ask a simple question:
The name is_prime explains the intention immediately. All mathematical details of the check stay inside the function.
Naive approach
Without a separate function, we can place the whole prime check directly inside the input loop:
This program works, but input, output, and the mathematical check are mixed together. If we need the same check somewhere else, we must copy the code. If we later improve or fix it, we must remember to change every copy.
A function does not automatically make the algorithm faster. It makes the program easier to organize and lets us reuse one correct implementation.
Core concept
Defining a function
The general form of a function is:
For example, this function calculates the square of an integer:
Each part has a meaning:
int before the name is the return type. It says that the function gives an integer back to its caller.
square is the function name.
int x is a parameter. It is the input received by the function.
The code between { and } is the function body.
return x * x finishes the function and sends the calculated value back.
The function definition only describes what the function does. Its body runs when we call it:
Here, 7 is an argument of the call. The parameter x receives this value, the function returns 49, and that answer is stored in value.
Parameters and arguments are closely related, but they are not the same:
Parameters are the placeholder variables written in the function definition.
Arguments are the actual values supplied during a particular call.
A separate set of local variables for every call
Every call creates its own parameters and local variables. Consider:
During the first call, the local parameter is x = 3. That call finishes and returns 9. During the second call, a brand-new local parameter is created with x = 5, and the function returns 25. The calls do not share the same x.
This is an important mental model: the caller pauses at the function call, the called function performs its job using its own local variables, and then control returns to the caller.
Re-entering the same function: square(3) and square(5)
C++
source · C++line 7
1int square(int x) {
2 int result = x * x;
3 return result;
4}
5
6int main() {
7 int a = square(3);
8 int b = square(5);
9 cout << a << " " << b;
10 return 0;
11}
Call square(3) from main
L7
main() reaches square(3). Execution in main pauses while control jumps to square.
inv
Each function invocation creates its own isolated local variables.
call stack
1main()
2square(3)
a-b-x⤳3result-
calls1
L7
01 / 10
Returning a value
A function whose return type is not void must return a value of the expected type. For example:
As soon as C++ executes return, the current function ends. No later statement in that function is executed. This makes early returns useful for handling special cases:
The return type can be any suitable C++ type, such as int, long long, bool, double, or string. The caller may store the returned value, print it, use it in an expression, or use it directly as a condition.
Functions that do not return an answer (void)
Some functions perform an action instead of calculating a value. We use the return type void for them:
We call this function as a separate statement:
We cannot write int x = print_answer(42) because a void function does not produce a value. A void function may use return; to stop early, but it does not return an expression after the word return.
Note on solve() in Competitive Programming:
In many competitive programming problems (especially those with multiple test cases), programmers often organize the logic for a single test case into a custom void solve() function and call it from main(). While we will see that structure in future lessons, for now we will place our input and control loops directly inside main().
Passing by value
Normal parameters are passed by value. The function receives a copy, so changing the parameter does not change the original variable:
If we call this function with a = 5, the local copy x becomes 6, but a remains 5 after the call.
Passing by value is the natural choice when a function only needs to read a small value or work with its own independent copy. It also protects the caller from accidental changes.
Passing by reference
If a function should change the original variable, we add & to the parameter type:
Now x refers directly to the variable supplied by the caller. If b = 5 and we call add_one_ref(b), then b becomes 6.
References are useful when a function must update one or several values. For example, a swap function needs access to the original variables:
For large objects such as vectors, copying can be expensive. If a function only needs to read such an object, we commonly pass it as a constant reference:
The & avoids copying the whole vector (O(1) overhead), while const guarantees that the function cannot modify its elements.
Passing by value vs. passing by reference
C++
source · C++line 10
1void add_one_val(int x) {
2 x++;
3}
4
5void add_one_ref(int &x) {
6 x++;
7}
8
9int main() {
10 int a = 5;
11 add_one_val(a);
12
13 int b = 5;
14 add_one_ref(b);
15
16 return 0;
17}
Initialize a = 5 in main
L10
Variable a is allocated in main() with initial value 5.
inv
Pass-by-value copies the argument; pass-by-reference binds directly to the caller's variable.
call stack
1main()
a⤳5b-x (val)-x (ref)-
modestart
L10
1 / 9
Local and global scope
A variable declared inside a function is local to that function. Code outside the function cannot access or modify it. Even if two different functions have a local variable with the same name, they are completely separate variables.
Variables declared outside all functions are global. Every function defined below their declaration can read and modify them:
Here, total_calls is global, so both process and main can access it. However, factor exists only inside process, so attempting to use it in main results in a compilation error.
Global arrays and variables are common in competitive programming when several helper functions must operate on the same large structure. However, variables that belong to only one function should remain local.
Definition order and function declarations
C++ reads the file from top to bottom. Before a function can be called, the compiler must already know its name, return type, and parameters. The simplest solution is to define helper functions before the main() function that calls them:
Another option is a function declaration (also called a prototype):
The declaration ends with a semicolon and has no body. It promises the compiler that the full definition will appear later. For beginner solutions, placing helper function definitions above main() is the standard and simplest approach.
Giving each function one clear responsibility
A well-designed function should have one clear job that can be described by its name. In our prime-checking program:
is_prime(x) answers one mathematical question: is x prime?
main() controls reading input and printing output.
The is_prime function does not print "YES" or "NO" directly, because printing output is not part of determining mathematical primality. Keeping calculation separate from I/O makes is_prime(x) reusable for other algorithms, such as counting primes or finding prime factors.
Main algorithm
We divide the program cleanly into two parts:
is_prime(x): Handles the mathematical primality check.
Numbers smaller than 2 are not prime (return false).
For every other number, check potential divisors d starting from 2 while d2≤x. If a divisor is found, return false immediately.
If no divisor divides x, return true.
main(): Configures fast I/O, reads q, loops q times reading each x, calls is_prime(x), and prints YES or NO.
Algorithm:
Implementation
Complexity
For q numbers whose values are at most M:
Time Complexity:O(qM) — each number x takes at most O(x) trial divisions.
Auxiliary Space:O(1) — no extra arrays or recursive stacks required.
1. Function is_prime(x)
1.1. If x < 2, return false
1.2. For d = 2 while d * d <= x
1.2.1. If x % d == 0, return false
1.3. Return true
2. Function main()
2.1. Configure fast input and output
2.2. Read q
2.3. Repeat q times:
2.3.1. Read x
2.3.2. Call is_prime(x)
2.3.3. If the returned value is true, print YES
2.3.4. Otherwise, print NO
2.4. Return 0
if(is_prime(x)){ cout << "YES\n";}
int q;cin >> q;while(q--){ int x; cin >> x; bool prime = true; if(x < 2){ prime = false;} for(int d = 2; 1ll * d * d <= x; d++){ if(x % d == 0){ prime = false; break;}} if(prime){ cout << "YES\n";} else{ cout << "NO\n";}}
return_type function_name(parameters){ // function body}
int square(int x){ return x * x;}
int value = square(7);
int a = square(3);int b = square(5);
int maximum(int a, int b){ if(a > b){ return a;} return b;}
void print_answer(int x){ cout << "Answer: "<< x << "\n";}
print_answer(42);
void add_one_val(int x){ x++;}
void add_one_ref(int &x){ x++;}
void swap_values(int &a, int &b){ int temp = a; a = b; b = temp;}
long long get_sum(const vector<int> &a){ long long sum = 0; for(int x : a){ sum += x;} return sum;}
#include <bits/stdc++.h>using namespace std;int total_calls = 0;void process(int x){ int factor = 2; total_calls++; cout << x * factor << "\n";}int main(){ process(5); process(10); cout << total_calls << "\n"; // cout << factor; // Compilation error: factor is local to process() return 0;}
bool is_prime(int x){ // implementation return true;}int main(){ // is_prime can be called here because it was defined above if(is_prime(7)){ // ...} return 0;}
bool is_prime(int x); // Declaration (prototype)int main(){ // is_prime can be called here because the compiler knows its signature if(is_prime(7)){ // ...} return 0;}// Full definition can appear below main()bool is_prime(int x){ // implementation return true;}
#include <bits/stdc++.h>using namespace std;bool is_prime(int x){ if(x < 2){ return false;} for(int d = 2; 1ll * d * d <= x; d++){ if(x % d == 0){ return false;}} return true;}int main(){ ios_base::sync_with_stdio(false); cin.tie(nullptr); int q; cin >> q; while(q--){ int x; cin >> x; if(is_prime(x)){ cout << "YES\n";} else{ cout << "NO\n";}} return 0;}