Functions are first-class in Kotlin. Learn default and named arguments, single-expression functions, lambdas and higher-order functions, and extension functions — the feature that lets you add methods to any type.
Kotlin functions support default parameter values and named arguments, which together eliminate the overloading and builder boilerplate common in Java. A single function definition covers many call shapes: callers omit parameters that have defaults and name the ones they pass for clarity. Single-expression functions drop the braces and return entirely.
// default values + a single-expression body (no braces, no 'return')
fun greet(name: String, greeting: String = "Hello", excited: Boolean = false) =
"$greeting, $name${if (excited) "!" else "."}"
fun main() {
println(greet("Ada")) // Hello, Ada.
println(greet("Ada", "Hi")) // Hi, Ada.
println(greet("Ada", excited = true)) // Hello, Ada! (named arg)
// named args let you skip 'greeting' and set only 'excited'.
}Functions are values in Kotlin: you can store a lambda in a variable, pass it to another function, and return it. A higher-order function takes or returns a function, which is how the collections API (and much of Android) works. When a lambda is the last argument, Kotlin lets you move it outside the parentheses — the trailing-lambda syntax you see everywhere.
// a lambda stored in a value; type is (Int, Int) -> Int
val add = { a: Int, b: Int -> a + b }
// a higher-order function: takes a function as a parameter
fun applyTwice(x: Int, op: (Int) -> Int): Int = op(op(x))
fun main() {
println(add(2, 3)) // 5
println(applyTwice(5) { it * 2 }) // 20 (trailing lambda, 'it' = single arg)
// when the lambda is the LAST arg, it moves outside the (): applyTwice(5) { ... }
}Extension functions let you add new methods to any existing type — even ones you do not own, like String or a library class — without subclassing. They are resolved statically and read as if the method always existed, which keeps call sites clean. Kotlin’s standard library and Android APIs use them heavily to add convenience to platform types.
// add a method to String without touching its source
fun String.shout(): String = this.uppercase() + "!"
// extensions can be generic and take arguments
fun <T> List<T>.second(): T = this[1]
fun main() {
println("hello".shout()) // HELLO! — reads like a built-in method
println(listOf(10, 20, 30).second()) // 20
}
// used everywhere in Kotlin/Android to add convenience to existing types.Kotlin’s scope functions run a block in the context of an object, differing in whether the object is it or this and what they return. apply returns the object (great for configuring it), let transforms it, run computes a result, also performs a side effect. They make object setup and transformation chains concise — common in Android configuration code.
class Config { var host = ""; var port = 0 }
fun main() {
// apply: configure an object, RETURN the object (this = the object)
val cfg = Config().apply {
host = "localhost"
port = 8080
}
// let: transform a value, return the block's result (it = the value)
val url = cfg.let { "${it.host}:${it.port}" } // "localhost:8080"
// also: do a side effect (e.g. log), return the object unchanged
val checked = cfg.also { println("configured ${it.host}") }
println(url)
}