Kotlin Functions

Write reusable functions with fun, typed parameters and return values, single-expression functions, default and named arguments, and vararg.

Lesson 8 of 43Beginner19 min read

As programs grow, you find yourself writing the same steps again and again: calculating a price with tax, formatting a date, checking a password. A function packages those steps under a name, so you write them once and call them whenever you need them.

Kotlin functions are declared with fun, and you've already written one: main. Functions can take parameters (inputs) and return a result. Kotlin adds some genuinely useful touches: parameters can have default values, callers can name the arguments they pass, and a function whose body is a single expression can be written on one line.

In this lesson you'll write functions with parameters and return values, shorten them to single expressions, use default and named arguments to make calls clearer, accept any number of arguments with vararg, and return two values at once.

Declaring and calling functions

Kotlin
fun greet(name: String) {
    println("Hello, $name!")
}

fun area(width: Double, height: Double): Double {
    return width * height
}

greet("Ada")
val room = area(4.0, 3.5)
  • Each parameter has a name and a type: name: String.
  • The return type comes after the brackets: : Double. return sends the result back.
  • A function that returns nothing useful has the type Unit, which you can leave out, as in greet.
  • Parameters are read-only, like val.

Single-expression functions

When a function's body is one expression, replace the braces and return with =:

Kotlin
fun area(width: Double, height: Double) = width * height
fun isAdult(age: Int) = age >= 18
fun label(score: Int) = if (score >= 50) "Pass" else "Fail"

Kotlin infers the return type from the expression. For public functions that other code relies on, writing the return type explicitly is still good practice: fun area(w: Double, h: Double): Double = w * h.

Default and named arguments

Give a parameter a default value and callers can leave it out:

Kotlin
fun order(item: String, size: String = "medium", sugar: Int = 0) =
    "$size $item with $sugar sugar"

order("tea")                         // medium tea with 0 sugar
order("coffee", "large")             // large coffee with 0 sugar
order("tea", sugar = 2)              // medium tea with 2 sugar

Named arguments (sugar = 2) say which parameter each value is for. They let you skip parameters with defaults, and they make calls with several similar values, like createUser("Ana", true, false), much easier to understand: createUser("Ana", isAdmin = true, sendEmail = false).

vararg, Pair and local functions

vararg accepts any number of arguments, which arrive as an array:

Kotlin
fun average(vararg numbers: Int): Double = numbers.average()
average(4, 8, 9)

To return two values, return a Pair with to, and unpack it with destructuring: val (low, high) = minMax(list). For three or more values, a data class is clearer.

Functions can also be declared inside other functions when they're only useful there, keeping helper code close to where it's used.

Passing a list to a vararg parameter

A vararg parameter wants separate values, so passing a whole list doesn't work directly. Put the values in an array and add the spread operator * in front of it:

Kotlin
fun average(vararg numbers: Int): Double = numbers.average()

val scores = listOf(4, 8, 9)
println(average(*scores.toIntArray()))          // 7.0
println(average(1, *intArrayOf(2, 3), 10))      // 4.0

The * unpacks the array into individual arguments, and you can mix it with ordinary values, as in the second call. For an Int vararg you need an IntArray, which is what toIntArray() gives you; for a String vararg you'd use toTypedArray().

Overloading, recursion and the Unit and Nothing types

Two functions can share a name as long as their parameter types differ. This is called overloading, and Kotlin picks the right version from the arguments:

Kotlin
fun format(price: Double) = "£%.2f".format(price)
fun format(price: Int) = "£$price.00"

format(3.5)   // £3.50
format(3)     // £3.00

Before writing overloads, check whether a default argument would do the same job with a single function. Defaults usually lead to less code.

A function can also call itself. This is recursion, and it suits problems that are defined in terms of a smaller version of themselves:

Kotlin
fun factorial(n: Int): Long = if (n <= 1) 1 else n * factorial(n - 1)

tailrec fun gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b)

factorial(5) is 120. Every recursive function needs a stopping case, here n <= 1, or it calls itself until the program runs out of stack and crashes with a StackOverflowError. When the recursive call is the very last thing a function does, as in gcd, marking it tailrec lets the compiler turn it into a loop, so it can't overflow the stack.

Two special return types are worth recognising:

TypeMeaningExample
UnitFinishes normally but has no useful resultfun log(msg: String)
NothingNever finishes normally; it always throwsfun fail(msg: String): Nothing = throw IllegalStateException(msg)

Because Nothing fits anywhere, you can write val n = text.toIntOrNull() ?: fail("Not a number"), and n is still a plain Int.

Kotlin Functions Syntax

Function forms

fun name(a: Int, b: String = "x"): Boolean {
    return true
}
fun square(n: Int) = n * n
name(1)
name(1, b = "y")
fun sum(vararg n: Int) = n.sum()
fun bounds(list: List<Int>) = list.min() to list.max()
val (lo, hi) = bounds(listOf(3, 9, 1))

Kotlin Functions Summary

  • fun name(param: Type): ReturnType { } declares a function; Unit means no useful result.
  • Single-expression functions use = instead of braces and return.
  • Default values make parameters optional; named arguments make calls clear.
  • vararg accepts any number of arguments; Pair and destructuring return two values.
  • Keep functions small, focused and named after what they do.

Kotlin Functions Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Functions with return values

Kotlin
fun celsiusToFahrenheit(c: Double): Double {
    return c * 9 / 5 + 32
}

fun isFreezing(c: Double) = c <= 0

fun describe(c: Double): String {
    val f = celsiusToFahrenheit(c)
    val note = if (isFreezing(c)) " (freezing)" else ""
    return "$c°C is $f°F$note"
}

fun main() {
    println(describe(20.0))
    println(describe(-5.0))
}
Output
20.0°C is 68.0°F
-5.0°C is 23.0°F (freezing)
Line-by-line explanation
  1. Lines 1 to 3A function with a block body and an explicit return.
  2. Line 5A single-expression function; the Boolean return type is inferred.
  3. Lines 7 to 11One function can call others, keeping each one small.
Example 2

Default and named arguments

Kotlin
fun formatPrice(amount: Double, currency: String = "£", decimals: Int = 2): String =
    currency + "%.${decimals}f".format(amount)

fun main() {
    println(formatPrice(5.5))
    println(formatPrice(5.5, "$"))
    println(formatPrice(1234.5678, decimals = 0))
    println(formatPrice(decimals = 1, amount = 9.99))
}
Output
£5.50
$5.50
£1235
£10.0
Line-by-line explanation
  1. Line 1Two parameters have defaults, so only amount is required.
  2. Line 2The format string itself is built with a template: "%.2f", "%.0f" and so on.
  3. Line 7decimals is named, so currency keeps its default.
  4. Line 8With names, arguments can be given in any order. 9.99 rounds to 10.0 with one decimal.
Example 3

vararg and returning two values

Kotlin
fun average(vararg scores: Int): Double =
    if (scores.isEmpty()) 0.0 else scores.sum().toDouble() / scores.size

fun lowestAndHighest(scores: List<Int>): Pair<Int, Int> =
    scores.min() to scores.max()

fun main() {
    println(average(70, 85, 90))
    println(average())

    val (low, high) = lowestAndHighest(listOf(62, 91, 75, 48))
    println("Lowest $low, highest $high")
}
Output
81.66666666666667
0.0
Lowest 48, highest 91
Line-by-line explanation
  1. Line 1vararg lets callers pass any number of Ints, even none.
  2. Lines 4 to 5to creates a Pair holding two values.
  3. Line 11Destructuring unpacks the Pair into two variables at once.

Common Kotlin Functions Mistakes

Missing parameter types

Don't
fun add(a, b) = a + b          // error: parameters need types
Do
fun add(a: Int, b: Int) = a + b

Block body without return

Don't
fun double(n: Int): Int {
    n * 2                      // error: a 'return' expression required
}
Do
fun double(n: Int): Int = n * 2

A positional argument after an out-of-order named one

Don't
order(size = "large", "tea")   // error: mixing named and positioned arguments is not allowed
Do
order(size = "large", item = "tea")

Once a named argument is out of its normal position, every argument after it must be named too.

Kotlin Functions in Real Projects

Functions in Kotlin projects

Android code is built from small functions: one to load data, one to format it, one to show it on screen. Kotlin's default and named arguments are used everywhere in Jetpack Compose, Android's modern UI toolkit, where a call like Text("Hi", fontSize = 18.sp, maxLines = 1) sets only the options you care about and leaves the rest at sensible defaults.

Kotlin Functions FAQ

What is the difference between a parameter and an argument in Kotlin?

A parameter is the name and type in the function's declaration, such as name: String in fun greet(name: String). An argument is the actual value you pass when calling it, such as "Ada" in greet("Ada"). Named arguments use the parameter's name to say which value goes where.

Can a Kotlin function return more than one value?

Not directly, but you can return a Pair or Triple and unpack it: val (low, high) = bounds(list). When the values deserve proper names, return a data class instead, such as data class Bounds(val low: Int, val high: Int), which keeps calling code readable.

Do I need to write the return type of a Kotlin function?

For a function with a block body that returns something, yes: without : Type, Kotlin assumes Unit. For a single-expression function written with =, the type is inferred, though writing it out on public functions documents your intent and catches accidental changes.

Can I change a parameter's value inside a Kotlin function?

No. Parameters are read-only, like val, so n = n + 1 inside the function is a compile error. Copy it into a local variable first, for example var count = n, and change that instead.

Where should I put functions that don't belong to a class?

At the top level of a file, outside any class. Kotlin doesn't need a utility class with static methods to hold them. Group related top-level functions in a file with a clear name, such as Pricing.kt, and mark ones that are only used in that file as private.

Kotlin Functions Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Complete the function so it returns the square of n.

 square(n: Int): Int = n * n
Predict the outputeasy

What does this print?

fun greet(name: String = "friend") = "Hi, $name"
println(greet())
Predict the outputmedium

What does this print?

fun box(w: Int = 1, h: Int = 1) = w * h
println(box(h = 5))
Multiple choicemedium

What is the return type of a function that only prints?

Multiple choicehard

Which call is valid for fun send(to: String, subject: String = "Hi", urgent: Boolean = false)?

Kotlin Functions Quiz

5 questions to check what you learned.

  1. Which keyword declares a function in Kotlin?

    • function
    • fun
    • def
    • void
  2. What does a single-expression function use instead of braces and return?

    • =>
    • =
    • :
    • ->
  3. Why use named arguments?

    • They run faster
    • They're required for every call
    • They make parameters mutable
    • They make calls clearer and let you skip parameters that have defaults
  4. What does vararg allow?

    • Passing any number of arguments of that type
    • Variable return types
    • Changing parameters inside the function
    • Calling a function with no name
  5. Can you reassign a parameter inside a Kotlin function, like n = n + 1?

    • Yes
    • Only for Int parameters
    • No, parameters are read-only like val
    • Only with default values