gl/ripetizioni

Functions and parameters: by value and by reference

Functions, formal and actual parameters, pass by value and by reference in C++ and Python: theory, typical mistakes and three worked examples.

by Gaetano Livornese

Read the original in Italian

End of September 2026: in Year 3 classes of the liceo scientifico (the Italian science-focused high school), applied-sciences option, the computer science syllabus has just closed the revision of selection and loops and in October is entering the chapter on functions and parameters. It is the same point in the syllabus that Year 3 classes at the technical institutes, computer science track, reach in the same period — the school context changes, not the topic. The first practical test in the lab on this theme typically falls six to eight weeks from here, so in November: there is time to build a solid mental model, instead of learning the syntax by heart.

Functions are the first real leap of abstraction after loops and arrays: no longer “a program that does one thing”, but “a program divided into pieces that call each other”. And it is precisely in the passing of data between these pieces — the parameters — that the most insidious mistake in the whole chapter lurks: believing that a function can always modify what it receives, and discovering in the test that it is not so.

Why functions: breaking the problem down

A function exists for two reasons, and it is worth keeping them apart from the start because the test asks for both:

  1. Reuse: you write once the code that calculates, say, the maximum of an array, and call it whenever you need it, without copying it again.
  2. Decomposition: a complex problem reads better (and is written with fewer mistakes) if you split it into smaller sub-problems, each with a name and a precise task.

A function has a signature (or prototype): type of the returned value, name, and the list of formal parameters — the “placeholders” that appear in the definition. When the function is called, it is given the actual parameters (or arguments): the concrete values of that specific call.

// formal parameter: n
int square(int n) {
    return n * n;
}

int main() {
    int result = square(5);  // 5 is the actual parameter
    std::cout << result << std::endl;
    return 0;
}

The commonest mistake: return versus printing to the screen

The first thing a practical test checks, often without saying so explicitly, is whether you understand the difference between a function that returns a value with return and one that merely prints it to the screen with printf/std::cout (or print() in Python). They are two different things:

int square(int n) {
    return n * n;      // returns a value: the caller can use it
}

void printSquare(int n) {
    std::cout << n * n << std::endl;   // just prints: returns nothing
}

If you write int x = printSquare(5); the compiler complains, because a void function does not produce a value to assign. It is a compile error, so in theory “easy to catch” — but in practice the opposite often happens: a function declared int that forgets the return in some branch of the code, and the compiler in certain cases does not notice straight away, whereas the theory paper does.

Visibility of variables: local and global

Every variable has a scope, that is, a portion of code in which it exists and is visible. Variables declared inside a function (including the formal parameters) are local: they are born when the function is called and die when it ends. A variable declared outside every function is global: it exists for the whole duration of the program and is visible everywhere.

int counter = 0;   // global

void increment() {
    counter++;      // modifies the global variable
    int local = 10;   // exists only inside increment()
}

The practical rule I always give: use global variables as little as possible. A function that reads and writes global variables is hard to test on its own and hides side effects that the theory paper loves to ask you to spot (“what is counter after three calls to increment()?”).

Pass by value: the function works on a copy

In C++, by default, a parameter is passed by value: the function receives a copy of the data, not the original. Any change inside the function stays local to that copy.

void swap_doesnt_work(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 3, y = 7;
    swap_doesnt_work(x, y);
    std::cout << x << " " << y << std::endl;   // still prints 3 7
    return 0;
}

The swap does happen, but only on the local copies a and b: when the function ends, those copies are destroyed and x, y in the caller stay unchanged. It is conceptual mistake number one of the chapter, and it is almost always asked in a question of the type “what does this program print?”.

Pass by reference: the function works on the original

To make a function really modify the caller’s variable, in C++ you use the reference, with the & in the parameter declaration:

void swap_values(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 3, y = 7;
    swap_values(x, y);
    std::cout << x << " " << y << std::endl;   // prints 7 3
    return 0;
}

int &a means “a is another name for the same variable you passed me”: it is not a copy, it is the caller’s variable seen under a different name. Every change to a inside the function is a direct change to x outside. Comparing the two examples — swap_doesnt_work and swap_values — side by side, and understanding exactly why one prints 3 7 and the other 7 3, is the mental exercise that really unlocks the topic.

Arrays passed to a function: a special case

With arrays in C++ the situation is different from a single int, and it is a common source of confusion: an array passed as a parameter is not copied. In C++ (as in C) the name of an array decays to the address of its first element, so the function in effect receives a pointer to the original array — and can modify it, even without writing &:

void double_all(int arr[], int length) {
    for (int i = 0; i < length; i++) {
        arr[i] = arr[i] * 2;
    }
}

int main() {
    int grades[4] = {5, 6, 7, 8};
    double_all(grades, 4);
    // now grades = {10, 12, 14, 16}: really modified
    return 0;
}

Here there is no need for &: the array is already, in substance, passed “by reference”. It is the reason why the very same problem — “does this function modify the caller or not?” — has a different answer depending on whether the parameter is a simple int or an array, and the test loves to ask you to tell them apart in the same exercise.

And in Python? Neither pure “by value” nor “by reference”

In Python the classic value/reference distinction does not apply in the same way: we speak of pass by object reference (or by assignment). The key is whether the passed object is mutable or immutable:

def increment(n):
    n = n + 1        # creates a new integer, does not modify the original

def add_element(items):
    items.append(0)   # modifies the original list, in place

x = 5
increment(x)
print(x)              # still prints 5: ints are immutable

grades = [6, 7, 8]
add_element(grades)
print(grades)            # prints [6, 7, 8, 0]: lists are mutable

An integer (int) in Python is immutable: n = n + 1 does not modify the object n points to, it creates a new one and assigns it to the local name n inside the function — the original outside stays intact. A list is mutable: items.append(0) modifies the very object items points to, and since both the parameter inside the function and the variable outside point to the same object in memory, the change is seen outside too. The very same reasoning — “can this data be modified in place?” — applies to arrays in C++ and lists in Python: the mechanism changes, not the question to ask.

Tracing the call stack by hand

The most reliable way not to get “what does this program print” wrong is to trace the sequence of calls in a table, row by row:

StepCallLocal variablesWhat happens to the caller
1main() startsx=3, y=7—
2swap_values(x, y) calleda and b are aliases of x, yno copy created
3temp = atemp=3—
4a = bx becomes 7direct modification
5b = tempy becomes 3direct modification
6function ends—x=7, y=3

Doing this table on paper, before running the program on the PC, is exactly the method I use in lessons with those who get stuck on this topic: it forces you to decide, row by row, whether you are creating a copy or touching the original.

Three worked examples

Exercise 1 — swapping two values.

In C++, with references (see above: swap_values(int &a, int &b) swaps x=3, y=7 into x=7, y=3). In Python the same result is obtained without a dedicated function, using multiple assignment — pass by reference is not needed because no function is needed:

x, y = 3, 7
x, y = y, x
print(x, y)   # 7 3

Exercise 2 — a function that modifies an array (or a list).

Write a function that zeroes all the negative elements of an array of integers.

void zero_negatives(int arr[], int length) {
    for (int i = 0; i < length; i++) {
        if (arr[i] < 0) {
            arr[i] = 0;
        }
    }
}

In Python, same logic on a list:

def zero_negatives(items):
    for i in range(len(items)):
        if items[i] < 0:
            items[i] = 0

grades = [5, -2, 8, -1, 3]
zero_negatives(grades)
print(grades)   # [5, 0, 8, 0, 3]

Exercise 3 — a function that returns two values.

In C++ a function returns a single value with return, so to “return two” you use parameters passed by reference as output channels:

void minMax(int arr[], int length, int &minimum, int &maximum) {
    minimum = arr[0];
    maximum = arr[0];
    for (int i = 1; i < length; i++) {
        if (arr[i] < minimum) minimum = arr[i];
        if (arr[i] > maximum) maximum = arr[i];
    }
}

int main() {
    int grades[5] = {6, 8, 5, 9, 7};
    int min, max;
    minMax(grades, 5, min, max);
    std::cout << min << " " << max << std::endl;   // 5 9
    return 0;
}

In Python the problem does not exist: a function can directly return a tuple with several values.

def min_max(items):
    return min(items), max(items)

grades = [6, 8, 5, 9, 7]
minimum, maximum = min_max(grades)
print(minimum, maximum)   # 5 9

It is worth pointing out this difference in lessons: what in C++ requires references as additional “outputs” of the function is, in Python, simply the commonest case of a multiple return — it is not that Python is “smarter”, it is that the language treats the concept of a returned value differently.

Where it leads next

The natural next step, which your syllabus will tackle later, is recursion — a function that calls itself: we have not dealt with it here on purpose, because the post on nested loops and arrays in Year 3 computer science already introduces it along with the rest of the Year 3 syllabus. If instead your track works mostly in Python and you want a broader view of the language, the Python roadmap in 6 weeks covers functions, lists and applications to maths and physics. And if in the future you tackle “pure” C at university, pointers explained well takes up exactly this discussion of addresses and references, one level deeper.

How I work on this topic in lessons

With those who arrive stuck on parameters, the first step is not re-explaining the syntax of &: it is having you trace by hand, as in the table above, two or three function calls — one by value, one by reference, one with an array — until it becomes obvious on its own why one change “shows” outside and the other does not. Once the mental model is right, the syntax is learned in ten minutes.

To practise on this and on neighbouring topics, in /simulazioni/liceo/ you will find tests organised by subject: on functions and parameters in computer science we do not yet have a dedicated simulation, and I tell you so openly instead of sending you to something that does not fit.

Computer science lessons are online and one-to-one: we start from where you got stuck — often precisely “why this function doesn’t modify what I pass it” — and build from there. If you want to talk about it first, we are at /contatti/.

Keep reading

Want to put what you read into practice?

Let's talk for 30 minutes, free: we look together at where you are stuck and I tell you honestly how I can help.

Or message me on WhatsApp.