Function Declarations
Why Functions Matter
Functions are the primary unit of code organization. A well-designed function does one thing, has a clear name, and can be tested in isolation. Kotlin's function syntax reduces boilerplate compared to Java while maintaining full expressiveness.
Basic Syntax
fun greet(name: String): String {
return "Hello, $name!"
}
// Usage
println(greet("Amazon")) // Hello, Amazon!
The return type goes after the parameter list. If a function returns nothing, the return type is omitted (Unit is implied):
fun printGreeting(name: String) {
println("Hello, $name!")
// Implicitly returns Unit
}
Single-Expression Functions
When a function body is a single expression, you can omit the braces and use =:
fun add(a: Int, b: Int): Int = a + b
// Return type is inferred
fun multiply(a: Int, b: Int) = a * b
Single-expression functions are not just shorter. They signal to the reader that the function is a simple transformation with no side effects.
Default Parameters
Kotlin eliminates method overloading for default values. You declare defaults directly in the signature:
fun log(message: String, level: String = "INFO") {
println("[$level] $message")
}
log("Server started") // [INFO] Server started
log("Disk full", "WARN") // [WARN] Disk full
In Java, you would need two overloaded methods. In Kotlin, one function handles both cases.
Named Arguments
When calling a function, you can name the arguments to improve clarity and skip defaults:
fun createUser(
name: String,
age: Int,
email: String = "",
active: Boolean = true
) {
println("$name, age $age, active=$active")
}
// Skip default parameters by name
createUser(name = "Alice", age = 30)
// Reorder named arguments
createUser(active = false, name = "Bob", age = 25)
Named arguments are especially valuable when a function has many parameters of the same type. Without names, it is easy to swap two String or Int arguments by mistake.
Explicit Return Types
Kotlin can infer return types for non-recursive functions, but public API functions should declare them explicitly for documentation:
// Inferred return type (fine for private/internal)
fun doubleIt(x: Int) = x * 2
// Explicit return type (better for public API)
fun doubleIt(x: Int): Int = x * 2
Lambdas and Higher-Order Functions
First-Class Functions
In Kotlin, functions are first-class citizens. You can store them in variables, pass them as arguments, and return them from other functions. This is the foundation of functional programming patterns.
Lambda Expressions
A lambda is an anonymous function defined inline:
val double: (Int) -> Int = { x -> x * 2 }
println(double(5)) // 10
The syntax is { parameters -> body }. When a lambda has a single parameter, you can use it instead of naming it:
val isEven: (Int) -> Boolean = { it % 2 == 0 }
println(isEven(4)) // true
Passing Lambdas to Functions
Functions that accept lambdas as parameters are called higher-order functions. The standard library is full of them:
val numbers = listOf(1, 2, 3, 4, 5)
// filter, map, reduce all take lambdas
val evens = numbers.filter { it % 2 == 0 } // [2, 4]
val doubled = numbers.map { it * 2 } // [2, 4, 6, 8, 10]
val sum = numbers.reduce { acc, i -> acc + i } // 15
Trailing Lambda Syntax
When the last parameter of a function is a lambda, you can move it outside the parentheses. When it is the only parameter, you can drop the parentheses entirely:
// These are all equivalent
numbers.filter({ it > 2 })
numbers.filter() { it > 2 }
numbers.filter { it > 2 } // Preferred
This trailing lambda syntax makes higher-order functions read like built-in language constructs.
Function Types
Every lambda has a function type. You can use them as parameter types and return types:
fun applyOperation(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
val sum = applyOperation(3, 4) { a, b -> a + b } // 7
val product = applyOperation(3, 4) { a, b -> a * b } // 12
Returning Values from Lambdas
The last expression in a lambda is the return value. Use return@label to exit from an enclosing function:
fun processNumbers(numbers: List<Int>) {
numbers.forEach {
if (it < 0) return@forEach // Skips negative, continues loop
println(it)
}
println("Done")
}
Lambdas vs Anonymous Classes
Kotlin lambdas compile to the same bytecode as Java anonymous classes when implementing single-abstract-method interfaces. This means lambdas work seamlessly with Java APIs:
// Kotlin lambda
view.setOnClickListener { println("Clicked") }
// Equivalent Java anonymous class
// view.setOnClickListener(new OnClickListener() { ... })
Practice Problems
Write a function transform that takes a String and a lambda (String) -> String. Apply the lambda to the string and return the result. Then call it with lambdas that uppercase the string and reverse it.
Solution
fun transform(input: String, operation: (String) -> String): String {
return operation(input)
}
fun main() {
val result1 = transform("hello") { it.uppercase() }
println(result1) // HELLO
val result2 = transform("hello") { it.reversed() }
println(result2) // olleh
} Write a function createProfile that accepts name (String), age (Int, default 0), bio (String, default empty), and isPublic (Boolean, default true). Use named arguments to call it with only the required parameters, then with custom values.
Solution
fun createProfile(
name: String,
age: Int = 0,
bio: String = "",
isPublic: Boolean = true
) {
println("$name, age=$age, bio=$bio, public=$isPublic")
}
fun main() {
createProfile(name = "Alice")
// Alice, age=0, bio=, public=true
createProfile(name = "Bob", age = 30, bio = "Engineer", isPublic = false)
// Bob, age=30, bio=Engineer, public=false
} Write a function processList that takes a List<Int> and a list of transform lambdas (List<(Int) -> Int>). Apply each transform in sequence to every element and return the final list.
Solution
fun processList(
numbers: List<Int>,
transforms: List<(Int) -> Int>
): List<Int> {
return transforms.fold(numbers) { current, transform ->
current.map(transform)
}
}
fun main() {
val result = processList(
listOf(1, 2, 3, 4),
listOf(
{ it * 2 }, // [2, 4, 6, 8]
{ it + 10 }, // [12, 14, 16, 18]
{ it * it } // [144, 196, 256, 324]
)
)
println(result) // [144, 196, 256, 324]
} Quiz
1. What is the advantage of default parameters in Kotlin over Java method overloading?
2. What does the trailing lambda syntax allow?
3. What does the following code print? ```kotlin val f: (Int, Int) -> Int = { a, b -> a * b } println(f(3, 4)) ```
4. When should you use an explicit return type on a Kotlin function?
Flashcards
Question
What is a higher-order function in Kotlin?
Click to reveal answer
Answer
A function that takes one or more functions as parameters, or returns a function. Examples: map, filter, sortedBy, run, let.
Question
What is the trailing lambda syntax?
Click to reveal answer
Answer
When the last parameter of a function is a lambda, you can move it outside the parentheses. If it is the only parameter, you can drop the parentheses entirely. Example: list.filter { it > 5 }.
Question
What does the 'it' keyword refer to in a Kotlin lambda?
Click to reveal answer
Answer
When a lambda has a single parameter, Kotlin implicitly names it 'it'. You can reference it without declaring it. Use the named form if there are multiple parameters or for clarity.
Question
How do you write a single-expression function in Kotlin?
Click to reveal answer
Answer
Use the = operator instead of braces and an explicit return. Example: fun double(x: Int) = x * 2. The return type is inferred.
Revision Notes
Key Takeaways
- 1. Use single-expression functions when the body is one expression.
- 2. Default parameters replace method overloading in Kotlin.
- 3. Named arguments prevent parameter order mistakes and improve readability.
- 4. Lambdas are first-class: pass them as arguments, store in variables, return from functions.
- 5. Trailing lambda syntax makes higher-order function calls read naturally.
Interview Tips
- • Explain the difference between a function reference (::functionName) and a lambda.
- • Know that Kotlin lambdas compile to anonymous classes for Java SAM interfaces.
- • Be ready to explain when default parameters are better than overloading.
- • Practice writing and passing lambdas to standard library functions like map, filter, and reduce.
Cheat Sheet
Functions & Lambdas Cheat Sheet
Function Declaration:
fun name(param: Type): ReturnType { body }- Single-expression:
fun name(param: Type) = expression
Default Parameters:
fun log(msg: String, level: String = "INFO")- Eliminates Java-style overloading
Named Arguments:
- Call with
func(name = "Alice", age = 30) - Can skip defaults, reorder arguments
Lambdas:
{ x -> x * 2 }— basic syntax{ it * 2 }— single parameter shorthand- Function type:
(Int) -> Int
Higher-Order Functions:
- Functions that accept or return lambdas
- Trailing lambda:
list.filter { it > 5 }