---
title: 'Passing by Value and by Reference'
source: 'https://academia.sh/en/courses/programming-fundamentals/passing-by-value-and-by-reference'
course: 'Programming Fundamentals'
language: en
updated: '2026-08-17T18:08:24+00:00'
license: 'CC BY-SA 4.0'
---

# Passing by Value and by Reference

Argument-passing models, the visibility of an in-function change from outside, and the discipline of side effects.

When a list is given to a function, is an element added to it inside the function
visible to the caller? The answer is not "it depends"; it depends on **precise rules**,
a direct extension of the two models introduced in the variables lesson.

This lesson defines those rules and distinguishes the three confusing terms.

## The Three Models

**Pass by value.** A **copy** of the argument is bound to the parameter; no change
inside the function affects the outside. For large data, the copy has a cost.

**Pass by reference.** The parameter is bound to the caller's variable **itself**; if
the function assigns it a new value, the caller's variable changes too.

**Pass by value of object reference.** The parameter is bound by **copying** the
object's address. The function and caller share the same object; assigning a new
object to the parameter changes only the local binding.

The third model is the most common and the most confused. It has two observable
consequences that appear to contradict each other:

```python
def append_in_place(values: list[int]) -> None:
    """Appends an element to the given list: the change is visible from outside."""
    values.append(99)

def rebind(values: list[int]) -> None:
    """Assigns a new list to the parameter: the outside is not affected."""
    values = [0, 0, 0]

measurements = [12, 18]

append_in_place(measurements)
print(measurements)          # [12, 18, 99]  — the object changed

rebind(measurements)
print(measurements)          # [12, 18, 99]  — the binding changed, not the object
```

The difference in one sentence: **modifying the object is visible from outside;
rebinding the name is not.** The function accesses the object the caller's variable
points to, not the variable itself.

## No Difference for Immutable Types

The distinction established in the types lesson is useful here: the content of an
immutable value cannot be changed, so the first behavior is impossible.

```python
def increment(number: int) -> int:
    number += 1            # only the local binding changed
    return number

value = 10
print(increment(value), value)      # 11 10
```

Numbers, strings, and similar immutable types therefore behave the same way under
every model. The confusion arises only with mutable types.

## Side Effect

The trace a function leaves in the outside world, apart from its return value, is
called a **side effect**: modifying an argument, updating a global variable, printing
to the screen, writing to a file.

A side effect is not inherently bad; a program must eventually act. The problem is a
side effect being **unexpected** — a function named `average` sorting the list
produces a change the caller did not account for.

It is possible to write the same task under two different contracts:

```python
def sorted_copy(measurements: list[int]) -> list[int]:
    """Returns a NEW sorted list; the input is not changed."""
    return sorted(measurements)

def sort_in_place(measurements: list[int]) -> None:
    """Sorts the given list IN PLACE; returns no value."""
    measurements.sort()

a = [12, 18, 7]
b = sorted_copy(a)
print(a, b)              # [12, 18, 7] [7, 12, 18]

sort_in_place(a)
print(a)                 # [7, 12, 18]
```

Both forms are legitimate. What matters is that the contract is visible in the name and
documentation. Functions that modify in place typically return no value — command–query
separation from the previous lesson applied in practice. A call that both returns a
value and modifies its input mixes the two contracts.

## Variadic Arguments

For some functions, the number of arguments is unknown in advance. Languages provide a
syntax that collects the remaining arguments into a single collection:

```python
def largest(*values: int) -> int:
    """Returns the largest of the given values. At least one value is required."""
    if not values:
        raise ValueError("at least one value is required")
    largest_value = values[0]
    for value in values:
        if value > largest_value:
            largest_value = value
    return largest_value

print(largest(12, 18, 7))          # 18
print(largest(*[12, 18, 7, 25]))   # 25  — the list was unpacked into separate arguments
```

The asterisk in the second call **unpacks** a collection into separate arguments. The
same symbol collects arguments in a definition and distributes them in a call — the two
uses are reverse of each other.

The cost of this flexibility is a less precise contract: argument count cannot be read
from the signature. If the arguments have different meanings — one a limit, another a
list of measurements — separate parameters are always clearer. Variadic arguments suit
only values of the **same type and equivalent role**.

## Defensive Copy

If a function stores data it receives, and the caller modifying it later would cause a
problem, the function must make a copy. This is called a **defensive copy**.

```python
class MeasurementRecord:
    def __init__(self, measurements: list[int]) -> None:
        self.measurements = list(measurements)     # defensive copy

outside_list = [12, 18]
record = MeasurementRecord(outside_list)
outside_list.append(99)                      # the outside list changed

print(record.measurements)                   # [12, 18]  — the record was not affected
```

Without the copy, the record's content could be silently changed from outside. The
`class` structure here is only an example in this course; it is introduced properly in
the paradigms topic.

The cost of the copy is memory and time. The shallow–deep copy distinction from the
variables lesson applies here too: copying only the outer shell of nested structures
does not fully break the sharing.

The same problem exists in the opposite direction: a function returning its stored
collection directly leaks internal state. The caller can modify the returned collection
and update the object's internals without going through its rules. This is the
return-side counterpart of the defensive copy: the collection handed out is either
copied or presented as an immutable view.

A third option is carrying the data in an immutable type; such data need not be
copied, and sharing it is safe. This preference of the functional programming approach
will be treated in the paradigms topic.

## Writing the No-Modification Promise into the Type

Some languages allow declaring, at the type level, that a parameter will not be
modified; an operation in the body that modifies it then produces a compile error.

This has two benefits: the contract lives in the signature rather than the
documentation, so the reader need not consult it, and the promise becomes provable — a
line added to the body later cannot silently break it.

In languages without such a declaration, the same promise is protected only by
documentation and review discipline. Preferring immutable types is then the most
reliable way to make it enforceable.

## Determining Which Model Is in Use

Which model a language uses can be determined with two small tests:

1. **Assign a new value** to the parameter. If the caller's variable changes, pass by
   reference is in use.
2. **Modify the object in place.** If this is visible on the caller's side, the object
   is being shared.

If neither is visible, it is pass by value; if only the second is visible, it is pass
by value of object reference — the shortest way to determine the model without
documentation.

## Summary

- In pass by value, a copy is passed; in pass by reference, the caller's variable
  itself is passed; in pass by value of object reference, the address is copied and the
  object is shared.
- The most common model is the third: modifying the object is visible from outside,
  rebinding the parameter is not.
- This distinction is not observed with immutable types; the confusion arises only with
  mutable types.
- A side effect is any trace beyond the return value; the problem is not its existence
  but it being unexpected.
- Both the in-place-modifying contract and the new-value-producing contract are
  legitimate; the choice is made visible in the name and the documentation.
- Data to be stored is given a defensive copy; immutable types remove this need.

## Next Step

Names defined inside a function body are not visible from outside; names from outside,
however, can be read from inside the body. What is the exact definition of these
visibility rules, and if a name is defined in more than one place, which one wins? The
next lesson takes up the rules of scope and lifetime.
