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.
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.
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
}
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?
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.
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".
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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?
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.
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 * heightThe 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