Functions Workbook

Fifteen short exercises on Kotlin functions — single-expression bodies, default and named arguments, vararg, and top-level and local functions. Solutions reveal on click.

Practice problems for In Kotlin, Functions Don’t Need a Class. Each takes a minute or two. Most exercises auto-check: implement the function in the editor and press Run — hidden tests go green when you’re right and red (with a hint) when you’re not, on JetBrains’ Kotlin compiler server. A few call-site and printing exercises stay attempt-then-reveal: press Run to try them, then click Show answer. Nothing here is a trick question, just direct practice of the syntax from the lesson.

Declaring and single-expression functions

1. Square a number

Implement square so it returns its Int argument multiplied by itself.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun square() { Assert.assertEquals("multiply the argument by itself", 25, square(5)) } } //sampleStart fun square(x: Int): Int = x // TODO: return x multiplied by itself //sampleEnd
Show answer Hide answer
fun square(x: Int) = x * x

2. The larger of two

Implement max(a, b) so it returns the larger of the two, using if as an expression.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun max() { Assert.assertEquals("return the larger of the two", 9, max(3, 9)) Assert.assertEquals("works when the first is larger", 9, max(9, 3)) } } //sampleStart fun max(a: Int, b: Int): Int = a // TODO: return the larger, using if as an expression //sampleEnd
Show answer Hide answer
fun max(a: Int, b: Int) = if (a > b) a else b

3. Tighten a block body

Rewrite this as a single-expression function:

fun double(n: Int): Int {
    return n * 2
}
Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun double() { Assert.assertEquals("return n times two", 42, double(21)) } } //sampleStart fun double(n: Int): Int = n // TODO: rewrite as a single expression returning n * 2 //sampleEnd
Show answer Hide answer
fun double(n: Int) = n * 2

4. A function that returns nothing

Write greet(name: String) that prints Hi, <name> and returns no meaningful value. What is its return type?

Try it — edit, then press Run fun main() { fun greet(name: String) { // TODO: print "Hi, " followed by name println("...") } greet("Ada") }
Show answer Hide answer
fun greet(name: String) {
    println("Hi, $name")
}

The return type is Unit — the default when you write no return type, and Kotlin’s stand-in for void.

5. Build a full name

Implement fullName(first, last) so it returns the two joined by a space, using a string template.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun fullName() { Assert.assertEquals("join first and last with a space", "Ada Lovelace", fullName("Ada", "Lovelace")) } } //sampleStart fun fullName(first: String, last: String): String = first // TODO: join first and last with a space //sampleEnd
Show answer Hide answer
fun fullName(first: String, last: String) = "$first $last"

Default and named arguments

6. A default port

Implement connect(host, port) with port defaulting to 8080, returning "host:port". Calling connect("localhost") should give "localhost:8080".

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun connect() { Assert.assertEquals("use the default port", "localhost:8080", connect("localhost")) Assert.assertEquals("override the port", "localhost:90", connect("localhost", 90)) } } //sampleStart fun connect(host: String, port: Int = 8080): String = host // TODO: return host and port joined by ":" //sampleEnd
Show answer Hide answer
fun connect(host: String, port: Int = 8080) = "$host:$port"

7. Skip to the argument you want

Given fun connect(host: String, port: Int = 443, timeoutMs: Int = 5000), write a call that keeps the default port but sets timeoutMs to 10000.

Try it — edit, then press Run fun main() { fun connect(host: String, port: Int = 443, timeoutMs: Int = 5000) = host + ":" + port + " (" + timeoutMs + "ms)" // TODO: keep the default port, set timeoutMs to 10000 println(connect("example.com")) }
Show answer Hide answer
connect("example.com", timeoutMs = 10000)

A named argument lets you skip over an earlier default.

8. Replace two overloads with one

In Java you’d write two methods: String label(String text) and String label(String text, boolean bold). Implement a single Kotlin function label where bold defaults to false: return the text wrapped in <b>…</b> when bold is true, otherwise the text unchanged.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun label() { Assert.assertEquals("plain text by default", "Hi", label("Hi")) Assert.assertEquals("bold wraps in <b>", "<b>Hi</b>", label("Hi", bold = true)) } } //sampleStart fun label(text: String, bold: Boolean = false): String = text // TODO: wrap in <b>...</b> when bold, else return text unchanged //sampleEnd
Show answer Hide answer
fun label(text: String, bold: Boolean = false) =
    if (bold) "<b>$text</b>" else text

9. Arguments out of order

Given fun rect(width: Int, height: Int), call it with the arguments written in the opposite order, using named arguments.

Try it — edit, then press Run fun main() { fun rect(width: Int, height: Int) = "" + width + "x" + height // TODO: call rect with the arguments in the opposite order, using named arguments println(rect(10, 20)) }
Show answer Hide answer
rect(height = 20, width = 10)

Named arguments free a call from positional order entirely.

vararg

10. Sum any number of values

Implement sum so it accepts any number of Int arguments and returns their total.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun sum() { Assert.assertEquals("total of all arguments", 6, sum(1, 2, 3)) Assert.assertEquals("no arguments totals zero", 0, sum()) } } //sampleStart fun sum(vararg n: Int): Int = 0 // TODO: return the total of all the arguments //sampleEnd
Show answer Hide answer
fun sum(vararg n: Int) = n.sum()

11. Pass an array into a vararg

Given fun sum(vararg n: Int): Int and val xs = intArrayOf(1, 2, 3), write the call that passes the array’s elements as the individual arguments.

Try it — edit, then press Run fun main() { fun sum(vararg n: Int) = n.sum() val xs = intArrayOf(1, 2, 3) // TODO: pass xs into sum as individual arguments (spread) println(sum()) }
Show answer Hide answer
sum(*xs)

The * is the spread operator — it unpacks the array into separate arguments.

12. Join words

Implement joinWords(vararg words) so it returns all the words joined by single spaces.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun joinWords() { Assert.assertEquals("join all the words with single spaces", "hello there friend", joinWords("hello", "there", "friend")) } } //sampleStart fun joinWords(vararg words: String): String = "" // TODO: join all the words with single spaces //sampleEnd
Show answer Hide answer
fun joinWords(vararg words: String) = words.joinToString(" ")

Top-level and local functions

13. A utility with no class

Implement gcd(a, b) as a top-level, single-expression recursive function (greatest common divisor).

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun gcd() { Assert.assertEquals("gcd of 48 and 36", 12, gcd(48, 36)) Assert.assertEquals("coprime numbers give 1", 1, gcd(17, 5)) } } //sampleStart fun gcd(a: Int, b: Int): Int = a // TODO: single-expression recursive gcd //sampleEnd
Show answer Hide answer
fun gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b)

A recursive single-expression function needs its return type written out, since it can’t be inferred from a body that refers to itself.

14. A helper that stays local

Inside a function banner(text: String), define a local function line() that prints 20 dashes, then use it above and below the text.

Try it — edit, then press Run fun main() { fun banner(text: String) { // TODO: define a local fun line() printing 20 dashes, then call it above and below println(text) } banner("Kotlin") }
Show answer Hide answer
fun banner(text: String) {
    fun line() = println("-".repeat(20))

    line()
    println(text)
    line()
}

line exists only inside banner — no helper leaks into the wider namespace.

15. Make a call self-documenting

The call setEnabled(true) tells a reader nothing. Given fun setEnabled(enabled: Boolean), rewrite the call so it reads itself.

Try it — edit, then press Run fun main() { fun setEnabled(enabled: Boolean) = println("enabled = " + enabled) // TODO: rewrite this call so it reads itself, using a named argument setEnabled(true) }
Show answer Hide answer
setEnabled(enabled = true)

Going deeper: tailrec, infix, and the = trap

16. Recursion that won’t overflow

A plain recursive sum blows the stack around a few thousand deep. Mark this one tailrec so it survives a million: sumTo(1_000_000) should return 500000500000.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun sumTo() { Assert.assertEquals(5050L, sumTo(100)) Assert.assertEquals(500000500000L, sumTo(1_000_000)) } } //sampleStart fun sumTo(n: Long, acc: Long = 0): Long = if (n == 0L) acc else sumTo(n - 1, acc + n) // TODO: make this stack-safe //sampleEnd
Show answer Hide answer
tailrec fun sumTo(n: Long, acc: Long = 0): Long =
    if (n == 0L) acc else sumTo(n - 1, acc + n)

The recursive call is in tail position, so tailrec lets the compiler rewrite it into a loop — no growing stack.

17. An infix power

Write an infix extension pow so 3 pow 4 is 81.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun pow() { Assert.assertEquals(81, 3 pow 4) Assert.assertEquals(1, 5 pow 0) } } //sampleStart infix fun Int.pow(e: Int): Int { return 0 // TODO: this raised to the power e } //sampleEnd
Show answer Hide answer
infix fun Int.pow(e: Int): Int {
    var result = 1
    repeat(e) { result *= this }
    return result
}

18. Spot the trap

What does makeGreeting() return, and what does makeGreeting()("Ada") print?

Try it — edit, then press Run fun makeGreeting() = { name: String -> "Hello, $name" } fun main() { // TODO: predict, then run println(makeGreeting()("Ada")) }
Show answer Hide answer

makeGreeting() returns a lambda (a function value of type (String) -> String), not a greeting — that’s the = { } trap. So makeGreeting()("Ada") calls the returned lambda and prints Hello, Ada. A block-bodied function would drop the =.

19. Default from another parameter

A default value can be computed from an earlier parameter. Make rect(5) return 25 (a square) and rect(5, 3) return 15.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun rect() { Assert.assertEquals(25, rect(5)) Assert.assertEquals(15, rect(5, 3)) } } //sampleStart fun rect(width: Int, height: Int): Int = width * height // TODO: make height default to width //sampleEnd
Show answer Hide answer
fun rect(width: Int, height: Int = width) = width * height

The default height = width is only evaluated when the argument is omitted.


Done? Head back to the lesson, In Kotlin, Functions Don’t Need a Class, or move on to the next one: control flow and ranges.

Comments