When calling a higher-order function, what are the different ways to pass the function argument (lambda, reference, stored value), and how do nullable and receiver function types appear in a HOF signature?
answer
- Forms: lambda, ::reference, stored value, anonymous fun
- Nullable: ((Int)->Int)? then f?.invoke(x)
- (Int)->Int? returns nullable, not nullable function
- Receiver type: Receiver.()->R gives implicit this
- Function params can have defaults
basics
~10 sYou can pass a lambda, a function reference like ::name or obj::method, or a stored function value. A function-type parameter can be nullable, like ((Int)->Int)?, and can declare a receiver, like Int.()->String.
solid answer
~40 sThree calling forms feed a HOF: a lambda ({ it + 1 } or { a, b -> a + b }), a callable reference (::topLevel, String::length, instance::method, or even Class::constructor via ::ClassName), and a stored value of the function type. Function-type parameters can be nullable: f: ((Int) -> Int)? = null, invoked safely with f?.invoke(x). They can carry a receiver: transform: Int.() -> String, where inside the lambda the receiver is this and at the call site you write number.transform() style — this is what makes scope functions and DSLs (apply, with, buildString) work. The arrow type can also name parameters for documentation: (index: Int, value: T) -> Unit. Default values for function-type params let callers omit the lambda entirely.
code
kotlin · 10 linesfun process(items: List<String>, transform: (String) -> Int): List<Int> =
items.map(transform)
fun main() {
val xs = listOf("a", "bb", "ccc")
println(process(xs) { it.length }) // lambda
println(process(xs, String::length)) // unbound member reference
val len: (String) -> Int = { it.length }
println(process(xs, len)) // stored value
}go deeper
Passes a lambda and knows it/named-param syntax.
Uses references and stored values, handles nullable function params correctly.
Designs receiver-typed parameters for DSLs and chooses the clearest passing form per API.
Sets API conventions (defaults, receiver vs plain, reference-friendly signatures) for library ergonomics.
## Three ways to supply the function argument Given `fun apply(x: Int, f: (Int) -> Int): Int = f(x)`: 1. **Lambda** — an anonymous function literal. Single param is implicitly `it`; multiple params are named: `{ a, b -> a + b }`. Trailing-lambda syntax applies when it is the last param: `apply(5) { it + 1 }`. 2. **Callable reference** — `::name` for a top-level/local function, `Type::member` for an unbound member (`String::length` has type `(String) -> Int`), `instance::member` for a bound one, and `::ClassName` for a constructor reference. 3. **Stored value** — any `val`/`var` whose type matches: `val g: (Int) -> Int = { it * 2 }; apply(5, g)`. There is also the **anonymous function** form `fun(x: Int): Int { return x + 1 }`, useful when you need an explicit return type or a non-local-free `return`. ## Nullable function types A parameter can itself be nullable; wrap the type in parentheses before `?`: ```kotlin fun run(x: Int, f: ((Int) -> Int)? = null): Int = f?.invoke(x) ?: x // safe call via invoke ``` Note `(Int) -> Int?` is different — that is a non-null function returning a nullable `Int`. Parentheses disambiguate. ## Function types with a receiver A **receiver function type** `Receiver.(Params) -> Return` makes the lambda body have an implicit `this` of type `Receiver`: ```kotlin fun buildGreeting(block: StringBuilder.() -> Unit): String { val sb = StringBuilder() sb.block() // call with sb as receiver return sb.toString() } buildGreeting { append("Hi"); append("!") } // 'this' is the StringBuilder ``` This is exactly how `apply`, `with`, `run`, `buildString`, and type-safe DSL builders are declared. ## Named params and defaults Arrow types may name parameters for readability: `onEach: (index: Int, value: T) -> Unit`. A function-type parameter can have a **default value**, letting callers omit it: `fun loop(n: Int, body: (Int) -> Unit = {})`. ## Choosing a form - Reference (`String::length`) is cleanest when an existing function already matches. - Lambda is best for inline, short logic. - Stored value when the same behavior is reused or chosen at runtime.
- What is the type of String::length?An unbound reference of type (String) -> Int; the receiver becomes the first parameter.
- Why parenthesize a nullable function type?((Int)->Int)? is a nullable function; (Int)->Int? is a non-null function returning Int?. The parentheses pick which part is nullable.
saying these in an interview costs you the question
- Confusing ((Int)->Int)? with (Int)->Int?
- Not knowing function references (::name, Type::member) are valid HOF arguments
- Calling a nullable function param with f(x) instead of f?.invoke(x)
- Unaware that receiver function types power apply/with/DSLs