Kotlin functions and lambdas interview questions, with answers
Kotlin treats functions as first-class values and gives them features Java needs overloads and utility classes for: default and named arguments, functions added to existing classes, trailing lambda syntax, and operators defined as ordinary functions. Interviewers use these questions to see whether you write idiomatic Kotlin, and whether you know the rules behind the syntax.
The answers below cover arguments, extension functions, lambdas, it and destructuring, function references, operator overloading and vararg, with code compiled with kotlinc 2.3 and run on the JVM. Then take the free Kotlin diagnostic — ten questions across every Kotlin topic in the bank — to see which of these you can explain but not yet predict.
The questions, with answers
1.What are default and named arguments in Kotlin?
In short: Parameters can declare default values, so callers may leave them out, and arguments can be passed by name, so any of them can be set in any order.
fun tag(text: String, bold: Boolean = false, size: Int = 12) can be called as tag("a"), with both defaults, or as tag("a", size = 9), which skips bold and sets size by name, as below. Named arguments make calls with several booleans or numbers readable, and default values replace the chains of overloads Java needs. Once a named argument is used, later positional arguments are only allowed if they are in their normal position. For Java callers, @JvmOverloads generates the overloads, since Java cannot use Kotlin's defaults.
fun tag( text: String, bold: Boolean = false, size: Int = 12, ) = "$text/$bold/$size" fun main() { println(tag("a", size = 9)) // a/false/9 }
2.What are extension functions in Kotlin?
In short: An extension function adds a function to an existing type from outside it, called with dot syntax as if it were a member, without inheriting from the type or changing its code.
fun String.initials() declares a function that can be called on any String, as s.initials() below. Inside it, this is the receiver string, so split can be called directly. Extensions are how Kotlin's standard library adds hundreds of functions to Java's classes, such as map and filter on lists, and how a project adds helpers to types it does not own. They do not modify the class: they cannot access private members, and they are compiled to static functions that take the receiver as a parameter. Extension properties, such as val String.lastChar, work the same way but cannot store state.
fun String.initials() = split(" ").map { it[0] } .joinToString("") fun main() { val s = "ada byron" println(s.initials()) // ab }
3.What are lambdas and higher-order functions in Kotlin?
In short: A lambda is a function literal in braces; a higher-order function takes functions as parameters or returns one, and a lambda passed last can go outside the parentheses.
twice below takes a function of type (Int) -> Int and applies it two times. The lambda { it * 3 } is written after the call's parentheses: when the last parameter is a function, Kotlin allows this trailing lambda syntax, which is what makes calls such as list.filter { ... } and DSL builders read naturally. Function types are ordinary types, so functions can be stored in variables and returned from other functions. Lambdas can capture and modify local variables of the enclosing function, unlike Java lambdas, which require effectively final variables.
fun twice( x: Int, f: (Int) -> Int, ) = f(f(x)) fun main() { val r = twice(2) { it * 3 } println(r) // 18 }
4.What is it in a Kotlin lambda?
In short: it is the implicit name of a lambda's only parameter; lambdas with more parameters name them before ->, and can destructure a pair or map entry with (k, v).
A lambda with exactly one parameter does not need to declare it: inside the braces, that parameter is called it, as in ws.map { it.length } below. Lambdas with two or more parameters must name them, as in { a, b -> a + b }, and naming the single parameter too is better style when the lambda is long or nested, since an inner it hides an outer one. Parameters that are pairs, data classes or map entries can be destructured with parentheses, so forEach { (k, v) -> ... } receives each map entry's key and value directly. An unused parameter can be named _.
val ws = listOf("kt", "go") println(ws.map { it.length }) // [2, 2] val m = mapOf("a" to 1) m.forEach { (k, v) -> println("$k=$v") } // a=1
5.What are function references in Kotlin?
In short: A function reference, written ::name, turns an existing function into a function value that can be passed wherever a lambda is expected.
When a lambda would only call an existing function, as in xs.filter { isOdd(it) }, a reference is shorter: xs.filter(::isOdd). References work for top-level functions, members (String::length), constructors (::User) and functions bound to a particular object (obj::method), and they have ordinary function types, so they can be stored and passed around. They make pipelines of named steps read clearly and avoid writing the same lambda several times. The referenced function must match the expected function type, so a function with extra parameters, even with defaults, may need a lambda instead.
fun isOdd(n: Int) = n % 2 == 1 fun main() { val xs = listOf(1, 2, 3, 5) println(xs.filter(::isOdd)) // [1, 3, 5] }
6.How does operator overloading work in Kotlin?
In short: Operators map to functions with fixed names, such as plus for +, get for [] and compareTo for <, and marking such a function operator makes the operator work for your type.
Each Kotlin operator is shorthand for a call to a function with a conventional name: a + b calls a.plus(b), a[i] calls a.get(i), a < b calls a.compareTo(b) < 0, and x in c calls c.contains(x). Declaring that function with the operator modifier, as plus in Rs below, lets the operator be used with your type; it can be a member or an extension. The set of operators is fixed, so no new symbols can be invented, and == always maps to equals. Operator overloading suits value types such as money, vectors and dates, where the meaning is obvious.
data class Rs(val p: Int) { operator fun plus(o: Rs) = Rs(p + o.p) } fun main() { println(Rs(40) + Rs(2)) // Rs(p=42) }
7.How do vararg parameters and the spread operator work in Kotlin?
In short: A vararg parameter accepts any number of arguments as an array, and the spread operator * passes an existing array's elements as separate arguments.
fun sum(vararg n: Int) can be called with no arguments or many, and inside the function n is an IntArray. To pass an array to a vararg parameter, prefix it with *, the spread operator, which can be mixed with individual values: sum(1, *arr) below adds 1, 4 and 5. Only one parameter may be vararg, usually the last; if it is not last, the arguments after it must be passed by name. listOf and arrayOf are themselves vararg functions. Spreading copies the array, so the function cannot modify the caller's array through its parameter.
fun sum(vararg n: Int): Int { return n.sum() } fun main() { val arr = intArrayOf(4, 5) println(sum(1, *arr)) // 10 }
How the diagnostic asks it
One question from the Kotlin bank, exactly as a sitting would show it. The bank has 4 on functions & lambdas and 30 across Kotlin.
What does this Kotlin program print?
fun greet( name: String, greeting: String = "Hello", end: String = "!", ) = "$greeting, $name$end" fun main() { println(greet("Ann", end = ".")) }
- 1Hello, Ann.correct
- 2Hello, Ann!
- 3., Ann!
- 4It does not compile: a named argument cannot skip greeting
Parameters with default values can be left out of a call, and a named argument sets a parameter by name regardless of position. "Ann" goes to name by position, end = "." sets end, and greeting keeps its default, "Hello", so the result is Hello, Ann.. Hello, Ann! ignores the named argument. ., Ann! assigns the second argument to greeting by position, which is what would happen without the name. Skipping a parameter with a default is exactly what named arguments allow, so it compiles. Default arguments replace most of the overloads Java code needs.
Measure it
Reading answers tells you what’s true. A diagnostic tells you what you get wrong.
10 Kotlin questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.