---
title: Functions
source: 'https://academia.sh/en/courses/kotlin/functions'
course: 'The Kotlin Language'
language: en
updated: '2026-10-21T05:02:39+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Declares a function with `fun` and uses parameters with default values, named arguments, and `vararg`'
    - 'Explains that a default value is computed again at every call and what this does to the equality criterion'
    - 'Measures that taking a function reference gives separate but equal objects'
---

# Functions

Introduces parameters with default values, named arguments, and the `vararg` parameter; measures that a function reference gives a separate but equal object each time it is taken.

In control flow we saw how to distribute a value to branches and how to walk over a range. In this lesson we package that flow into named, reusable pieces: functions. First we look at how the parameters of a function can be made flexible, then we ask the same question of the functions themselves: when we take the same function twice as a value, do we get two equal objects, or a single object?

## Making Parameters Flexible

In Kotlin, a function is declared with the `fun` keyword: the name, then the parameters inside parentheses in the form `name: Type`, then a colon and the return type. If the body is a single expression, it can be written with `=`. Three features make the parameter list flexible.

The first is the default value: if `= value` is added to a parameter, the caller can leave it out. The second is the named argument: if `punctuation = "?"` is written in the call, the value is bound by name, not by position, and the parameters in between stay at their defaults. The third is the `vararg` parameter: if a parameter is marked `vararg`, the caller can give as many values as they want, and inside the function these appear as an array. To pass an array you already have to a `vararg` parameter, a `*` is placed in front of the array.

In this lesson too, the measurements are made with the helper file introduced in the first lesson. The `Criterion` class takes a value, its twin, and a different value, and asks three questions; the file is given below exactly as it was.

```kotlin
// Criterion.kt — classifies the criterion that makes two values equal
class Criterion(val label: String, first: Any?, twin: Any?, other: Any?) {
    val sameObject = first === twin
    val equalTwin = first == twin
    val equalOther = first == other

    val name: String = when {
        sameObject -> "singleton"
        !equalTwin -> "identity"
        !equalOther -> "content"
        else -> "partial-content"
    }

    fun report() {
        println("$label: same-object=$sameObject, twin=$equalTwin, other=$equalOther -> $name")
    }
}

fun summarize(checks: List<Criterion>) {
    for (name in listOf("singleton", "identity", "content", "partial-content")) {
        println("$name: ${checks.count { it.name == name }}")
    }
}
```

A default value does not have to be a constant; it can be any expression. When the caller leaves the parameter out, that expression is computed at the moment of the call, so two calls of the same function can get two separate results from the default. A named argument is also not only a convenience of order: someone reading the call understands what the value belongs to from the writing `punctuation = "?"`, without looking at the declaration of the function. In the second section we will measure this behavior of the default value.

The program below uses the three features together. `greet` carries two parameters with default values, and `sum` takes a `vararg`.

```kotlin
// Main.kt — default value, named argument, and vararg
fun greet(name: String, greeting: String = "Hello", punctuation: String = "."): String =
    "$greeting, $name$punctuation"

fun sum(vararg numbers: Int): Int = numbers.sum()

fun main() {
    println(greet("Ada"))
    println(greet("Ada", punctuation = "?"))
    println(greet(greeting = "Hi", name = "Sam"))
    println(sum())
    println(sum(1, 2, 3))
    val values = intArrayOf(4, 5)
    println(sum(*values, 6))
}
```

```
Hello, Ada.
Hello, Ada?
Hi, Sam.
0
6
15
```

The first two lines show calls in which the name is given alone and in which the punctuation is changed with a named argument: when `greeting` and `punctuation` were left out, `Hello` and the period were used, and when `punctuation = "?"` was written, only the punctuation changed. In the third line the arguments were given out of order, by their names, and `Hi, Sam.` came out. The last three lines are for `vararg`: when no value was given, the total was `0`, and with three values it was `6`; `sum(*values, 6)` added the two elements of the array and the extra six together and gave `15`.

**LB38.** Default values and `vararg` were measured only with `String` and `Int` parameters; the same behavior for parameters whose type is a class was not tested separately in this lesson.

**LB39.** Reordering with named arguments was shown in only one call; the rules for mixing named and positional arguments in the same call are outside this lesson.

## Function Reference and Default Value

The measurement of the lesson looks at two things. The first is the function reference: the writing `::plain` gives the `plain` function as a value without calling it. Does using the same writing twice give the same object, or separate objects? The second is the default value: does the default `into: MutableList<Int> = mutableListOf()` build a new list at every call, or share a single list?

`collectMutable` adds an item to the list it receives and returns the list; if no list is given, the default empty mutable list is used. `defaultList` returns the `listOf()` default directly. We write three observations: `function-reference`, two `::plain` and, as the different value, `::other`; `default-mutableListOf`, `collectMutable(1)` twice and `collectMutable(2)` as the different value; `default-listOf`, `defaultList()` twice and `listOf(1)` as the different value.

```kotlin
// Main.kt — function reference and default values
fun plain(n: Int): Int = n

fun other(n: Int): Int = n + 1

fun collectMutable(item: Int, into: MutableList<Int> = mutableListOf()): MutableList<Int> {
    into.add(item)
    return into
}

fun defaultList(into: List<Int> = listOf()): List<Int> = into

fun main() {
    val checks = listOf(
        Criterion("function-reference", ::plain, ::plain, ::other),
        Criterion("default-mutableListOf", collectMutable(1), collectMutable(1), collectMutable(2)),
        Criterion("default-listOf", defaultList(), defaultList(), listOf(1)),
    )
    for (check in checks) {
        check.report()
    }
    summarize(checks)
}
```

```
function-reference: same-object=false, twin=true, other=false -> content
default-mutableListOf: same-object=false, twin=true, other=false -> content
default-listOf: same-object=true, twin=true, other=false -> singleton
singleton: 1
identity: 0
content: 2
partial-content: 0
```

The `function-reference` line shows `same-object=false` and `twin=true`: the two `::plain` writings gave separate objects, but these two objects came out equal by `==`. Separate objects, equal content: the content criterion. `default-mutableListOf` is also content. The two calls built two separate lists, and since both carry `[1]`, they are equal. The default value is computed again every time the function is called; an item added in one call does not leak into the next call.

`default-listOf`, on the other hand, is a singleton: `same-object=true`. The empty `listOf()` call gave the same object both times. The summary lines show one singleton, zero identity, two content, and zero partial content.

**LB40.** The equality of function references was measured only with top-level functions; how a reference bound to an object (`object::name`) is made equal was not measured in this lesson.

**LB41.** That the empty `listOf()` call gives a single object was measured on the JVM target of Kotlin 2.4.21; since this behavior depends on the implementation of the list, the same is not claimed for other versions.

## The Default Value Is Computed at Every Call

We saw that a default value can be an expression. The real question is when the expression runs: once, when the function is defined, or at every call? The block below takes the default value from a function that increments a counter, and also tries whether the default of a parameter can depend on the one before it.

```kotlin
// Main.kt — the default value is computed at every call
var counter = 0

fun nextId(): Int {
    counter++
    return counter
}

fun label(text: String, id: Int = nextId(), width: Int = text.length): String =
    "$text#$id/$width"

fun main() {
    println(label("ab"))
    println(label("abc"))
    println(label("abc", id = 100))
    println(label("abc", width = 1))
    println(counter)
}
```

```
ab#1/2
abc#2/3
abc#100/3
abc#3/1
3
```

The first call gave `ab#1/2`: `nextId()` ran for `id` and produced 1, and since the `width` default is `text.length`, it became 2. The second call gave `abc#2/3`; the counter was incremented a second time. So the default value is computed again at every call. In the third call `id` was given explicitly as 100 and `abc#100/3` was printed; in this case `nextId()` did not run at all. The fourth call gave `abc#3/1`: `width` became 1 through the named argument, the counter ran again for `id`, and produced 3. The last line gave the value of `counter` as `3`: of the four calls, only three used the default `id`.

This proves two things. First, the default expression runs at the moment of the call; if it has a side effect, it repeats at every call. Second, defaults can reach the parameters before them (`width` looks at `text`). The frequent mistake is to assume that the default value is frozen at definition; in Python, the evaluation of a mutable default value once at definition is a known trap, and there is no such trap in Kotlin. Java has no default parameters; the same effect is obtained with overloading.

**LB42.** The default value measurement was made with a top-level counter; the behavior of the counter in a multi-threaded environment was not measured in this lesson.

## Unit, Nothing, and a Local Function

A function whose return value is not written actually returns something. The block below prints a function that returns `Unit`, catches a function that returns `Nothing` inside a `try`, and uses a function placed inside another function.

```kotlin
// Main.kt — Unit, Nothing, and a local function
fun nothingToReturn() {
}

fun fail(message: String): Nothing = throw IllegalStateException(message)

fun total(values: List<Int>): Int {
    fun square(n: Int) = n * n
    var sum = 0
    for (v in values) {
        sum += square(v)
    }
    return sum
}

fun main() {
    println(nothingToReturn())
    println(nothingToReturn() === Unit)
    println(total(listOf(1, 2, 3)))
    val result = try {
        fail("stopped")
    } catch (e: IllegalStateException) {
        e.message
    }
    println(result)
}
```

```
kotlin.Unit
true
14
stopped
```

The first line gave `kotlin.Unit`: the function that we thought returned nothing actually returns the singleton object named `Unit`, and `println` can print it. The `true` in the second line shows that the comparison `nothingToReturn() === Unit` is true: `Unit` is a single object. This is a basic difference from `void` in Java; `void` is not a value, while `Unit` is a real value, and for this reason `Unit` can be used in generic types.

The third line is `14`, that is, the sum of 1, 4, and 9. The `square` function is defined inside `total` and can be called only from there. A local function can reach the variables of the outer function; this was not used here and the access was not measured.

The last line gave `stopped`. The return type of the `fail` function is `Nothing`: it never returns normally, it always throws an exception. The compiler knows this; since the `fail(...)` call had to produce a value inside `try`, the message of the caught exception became the result. The frequent mistake is to confuse the `Nothing` type with `Unit`. `Unit` returns and execution continues, while `Nothing` never returns.

**LB43.** The `Nothing` measurement was made with a function that throws an exception; marking functions that call an infinite loop or `exitProcess` with `Nothing` was not measured in this lesson.

## Overload Selection and an Extension Function

Functions that carry the same name are chosen according to the parameter type. The block below defines four `show` functions and looks at which one is chosen with five different calls. Then it writes extension functions on an `Int` and a `String`, and shows how a `MutableList` parameter affects the calling side.

```kotlin
// Main.kt — overload selection, extension function, and parameter
fun show(x: Int) = "Int"
fun show(x: Long) = "Long"
fun show(x: Any) = "Any"
fun show(x: String) = "String"

fun Int.isEven(): Boolean = this % 2 == 0

fun String.initials(): String = split(" ").joinToString("") { it.take(1).uppercase() }

fun modify(list: MutableList<Int>) {
    list.add(99)
}

fun main() {
    println(show(1))
    println(show(1L))
    println(show("a"))
    val any: Any = "a"
    println(show(any))
    println(show(1.5))
    println(4.isEven())
    println("ada lovelace".initials())
    val nums = mutableListOf(1)
    modify(nums)
    println(nums)
}
```

```
Int
Long
String
Any
Any
true
AL
[1, 99]
```

`show(1)` gave `Int`, `show(1L)` gave `Long`, and `show("a")` gave `String`: the compiler chooses the most specific match. The fourth line is interesting. The `any` variable carries a `String` at run time, but because its type was declared as `Any`, `show(any)` chose the `Any` version. Overload resolution is made at compile time, according to the declared type of the variable; the run-time type does not affect the choice. In Java too, overloading is resolved at compile time, while a virtual method call is resolved according to the run-time type, and the two must not be confused. `show(1.5)` gave `Any`, because it fits only the `Any` version.

`4.isEven()` gave `true` and `"ada lovelace".initials()` gave `AL`: an extension function adds a new form of call to an existing type without changing the type itself. An extension function is resolved statically; it is not a real member of a class.

The last line gave `[1, 99]`. The `modify` function added to the list it received as a parameter, and the calling side saw this. In Kotlin, the reference itself is copied as a value: the object is shared, but the function cannot bind the caller's variable to another object; the parameter variable cannot be reassigned anyway. The frequent mistake is to make a change inside a collection passed to a function and assume that it will stay local.

**LB44.** Overload selection was measured with four definitions and five calls; which version `Short` or `Byte` arguments go to, besides `Int` and `Long`, was not measured in this lesson.

## Habits That Come from the Measurements

The first thing the measurements show in a function definition is that the default value is an expression and runs again at every call. This removes the mutable-default trap of Python in Kotlin, but it creates another risk: if the default expression has a side effect, the number of calls affects the behavior of the program. In the measurement the `counter` value became three after four calls, because the default value was used in only three calls. Keeping default expressions free of side effects is a good habit for this reason; work such as incrementing a counter should be done in the body as an explicit call.

The distinction between `Unit` and `Nothing` is a useful part of the type system. We measured that a function returning `Unit` returns a singleton value; for this reason `Unit` is used in generic types without trouble. `Nothing` is the type of an expression that never returns, and in this way a writing such as `val x = y ?: fail("missing")` keeps `x` in the non-null type. The compiler knows that the code after the `fail` function will not run, so it can give an unreachable-code warning.

In overloading, the choice is made at compile time and looks at the declared type. Although the `any` variable carried a `String`, the `Any` version was chosen. This behavior is different from a virtual method call being resolved according to the run-time type, and code that confuses the two calls an unexpected version. As an overload set grows, it becomes harder to follow which call goes to which version; in that case separately named functions (`showInt`, `showLong`) can be more readable.

Extension functions add a form of call without changing the type. When you want to add a method to a library class, writing an extension is often enough instead of creating a subclass. But an extension is not a real member; it cannot reach the private fields of the class and is resolved statically, that is, it is not chosen according to the run-time type.

A mutable collection passed as a parameter changes on the calling side as well. If you do not want this, take the function parameter in the read-only `List` type or make a copy of it. The `[1, 99]` output in the measurement is a short proof of this trap.

## The Bounding Measurement

The content criterion holds for the function reference, but one should not read the fact that the reference is a separate object each time it is taken as a contract: what was measured is that the two takings come out equal by `==`. In the same lesson the `listOf()` default came out singleton. That is, the writing of a default value alone does not determine the criterion: `mutableListOf()` is content because it builds a new object at every call, and `listOf()` is a singleton because it returns the same empty list. When looking at the default of a parameter, the criterion depends on what that expression builds.

## Summary

- In a function declared with `fun`, parameters with default values, named arguments, and `vararg` make the call flexible; `*` spreads an array into a `vararg` parameter.
- The default value is computed again at every call; the `mutableListOf()` default is not shared between calls.
- Two function references such as `::plain` are separate objects but equal by `==`: content.
- The empty `listOf()` default gave the same object in two calls: singleton.
- This lesson's criterion: **content**; bounding measurement: the `listOf()` default is a singleton, and the `mutableListOf()` default is content.

## Next Step

We have seen how to take and pass functions as a value. In the next lesson we move on to writing a function in place: lambdas and higher-order functions. There we ask the same question for two lambdas: are two lambdas the same object, equal, or neither?
