Explain how Kotlin function types and lambdas relate to the `invoke` operator. What is `FunctionN` and how does calling a lambda work under the hood?
answer
- (Int)->String == Function1<Int,String>
- single member: operator fun invoke
- lambda = object subclassing Lambda
- f(x) compiles to f.invoke(x)
- inline avoids FunctionN allocation
basics
~20 sA function type like (Int) -> String is really an interface with one method, invoke. A lambda is an object implementing that interface. Calling f(x) just calls f.invoke(x), so lambdas and invoke are the same mechanism.
solid answer
~40 sKotlin function types are sugar over the `FunctionN` interfaces in `kotlin` (`Function0<R>`, `Function1<P1,R>`, … up to 22, plus `FunctionN` for higher arities). Each declares a single `operator fun invoke(...)`. So `(Int) -> String` == `Function1<Int, String>`. A lambda or function reference compiles to an object (often a `FunctionN` subclass, e.g. `Lambda`) whose `invoke` holds the body. When you write `f(3)`, the compiler emits `f.invoke(3)`. This unifies callables: a custom class defining `operator fun invoke(p: Int): String` is structurally callable just like a lambda, and you can even pass it where a function type's `invoke` shape matches by adapting it (e.g., `::someInstance` style or wrapping). Suspend function types map to `SuspendFunctionN` with a `suspend operator fun invoke`. Knowing this explains inlining (`inline` removes the `FunctionN` allocation), and why capturing lambdas allocate objects.
code
kotlin · 10 linesval f: (Int) -> Int = { it * 2 }
println(f(21)) // 42 -> f.invoke(21)
println(f.invoke(21)) // 42, explicit
// a custom class is structurally a callable too:
class Doubler : (Int) -> Int {
override fun invoke(p1: Int): Int = p1 * 2
}
val g: (Int) -> Int = Doubler()
println(g(21)) // 42go deeper
Knows f(x) calls a lambda but may not know it is invoke.
Maps function types to FunctionN and explains lambda-to-object compilation.
Connects to allocation, inline, suspend function types, and implementing function types directly.
Reasons about performance/binary surface of higher-order APIs and SAM/function-type interop.
## Function types are interfaces When you write a function type such as `(Int, String) -> Boolean`, Kotlin treats it as the generic interface `Function2<Int, String, Boolean>`. The standard library defines `Function0<R>` through `Function22<...>`, each with exactly one member: ```kotlin public interface Function1<in P1, out R> { public operator fun invoke(p1: P1): R } ``` So a function type **is** a type with a single `operator fun invoke`. That is the whole link to this topic: calling any function value is just invoking its `invoke`. ## Lambdas compile to objects A lambda `{ x: Int -> x.toString() }` becomes an object implementing the right `FunctionN` (the compiler typically generates a subclass of `kotlin.jvm.internal.Lambda`). Its `invoke` body is the lambda body. Therefore: ```kotlin val f: (Int) -> String = { it.toString() } f(3) // compiles to f.invoke(3) f.invoke(3) // identical, explicit form ``` ## Why this matters - **Allocation**: a non-inlined lambda is an object; capturing variables makes it stateful. Marking the higher-order function `inline` lets the compiler splice the body in and avoid the `FunctionN` allocation. - **Custom callables**: because a function value is just 'something with `invoke`', a class with `operator fun invoke` is conceptually the same shape, which is why callable objects can replace simple lambdas (and carry state/config). - **suspend**: a `suspend (T) -> R` maps to a suspend function interface whose `invoke` is itself `suspend`, so coroutine call sites still desugar to `invoke`. ## Function references `::println` or `obj::method` produce function objects too — again with an `invoke` carrying the call. This is why you can store them in a variable of a function type and call them with parentheses.
- Can a class directly implement a function type like `(Int) -> Int`?Yes. You can write `class Doubler : (Int) -> Int { override fun invoke(p1: Int) = p1 * 2 }` and use instances anywhere that function type is expected.
- How does `inline` change the lambda/`invoke` story?`inline` higher-order functions inline the lambda body at the call site, so no `FunctionN` object is allocated and there is no `invoke` call at runtime.
saying these in an interview costs you the question
- Saying function types are primitives with no underlying interface
- Claiming lambdas never allocate (non-inline ones do)
- Not knowing `invoke` is the single member of FunctionN
- Confusing inlining with reflection
- Asserting a class cannot implement a function type