Lambdas & Inline Functions Quiz
KOTLIN › Types & Classes
What is the primary performance benefit of marking a higher-order function inline?
- It caches the lambda's result so repeated calls skip recomputing
- It avoids allocating a function object and closure per lambda
- It runs the lambda on a background thread without extra setup
- It makes the function thread-safe by default for every caller
Answer: It avoids allocating a function object and closure per lambda
Inlining pastes the lambda body at the call site, eliminating the function-object and closure allocation that a normal higher-order call would require.
Inside a lambda passed to the inline function forEach, a bare return will:
- Cause a compile error, since lambdas never allow return.
- Return only from that lambda for the current item being handled.
- Return from the enclosing function that called forEach.
- Skip to the next element, like a continue statement would.
Answer: Return from the enclosing function that called forEach.
Because forEach is inline, the lambda body is inlined into the caller, so a bare return is a non-local return that exits the enclosing function.
You have inline fun run(crossinline body: () -> Unit). What does crossinline enforce?
- The lambda must not use any captured variables
- The lambda is excluded from inlining and becomes a real object
- The lambda may not perform a non-local return
- The lambda must be the function's last parameter
Answer: The lambda may not perform a non-local return
crossinline keeps the lambda inlined but forbids non-local returns, which is required when the lambda is invoked from a different execution context such as an object or nested function.
When is noinline required on a lambda parameter of an inline function?
- When the lambda has more than one parameter in its declaration
- When the lambda uses a non-local return from the inline call
- When the function also declares a reified type parameter
- When you need to store, return, or pass it as a normal object
Answer: When you need to store, return, or pass it as a normal object
Inlined lambdas have no object representation, so to treat one as a value (store it, return it, pass to a non-inline function) you must mark it noinline.
Which statement about reified type parameters is correct?
- They can be used on any generic function, even a non-inline one.
- They require the crossinline modifier to read the type value.
- They’re permitted only on inline functions, usable at runtime.
- They remove the need to mark the enclosing function inline.
Answer: They’re permitted only on inline functions, usable at runtime.
Reification depends on substituting the concrete type at each inlined call site, so reified is permitted only on inline functions; it then lets you use is T, as T, and T::class.
Regarding closures, how does Kotlin differ from Java?
- Kotlin lambdas cannot capture any outer-scope variables at all
- Kotlin can capture and modify a var from the outer scope
- Kotlin closures only capture val values, never mutable vars
- Kotlin copies captured variables by value, hiding later changes
Answer: Kotlin can capture and modify a var from the outer scope
Kotlin closures can read and mutate enclosing-scope variables, including a var, whereas Java requires captured locals to be final or effectively final.
Why can't a public inline function reference a private or internal declaration in its body?
- Because an inline function is not permitted to have any body
- The body is inlined into modules that can't access that symbol
- Because private members always run slower than public ones
- Because the compiler bans every generic inside inline functions
Answer: The body is inlined into modules that can't access that symbol
The body is inlined into calling modules, so a non-public symbol would not be visible there; @PublishedApi internal is the escape hatch for needed internal declarations.
A bare return inside an anonymous function such as fun(x: Int) { ...; return } passed to forEach behaves how, compared to the same return inside a lambda?
- It's a compile error, since anonymous functions cannot use return
- It returns non-locally from the enclosing function, just like a lambda
- It returns locally from the anonymous function, not the enclosing one
- It skips straight to the next iteration element, like continue
Answer: It returns locally from the anonymous function, not the enclosing one
An anonymous function has its own return scope, so a bare return exits only that anonymous function (a local return); the same return in a lambda would be a non-local return from the enclosing function.
When a lambda captures and mutates a local var, how does the Kotlin compiler typically implement that capture on the JVM?
- It wraps the var in a heap Ref.* holder so both share one cell
- It marks the variable volatile so every write is seen by all threads
- It copies the value into the lambda, so later mutations stay local there
- It rejects the code, because closures can capture only val values
Answer: It wraps the var in a heap Ref.* holder so both share one cell
To let both the enclosing scope and the lambda see the same mutable value, the compiler wraps the captured var in a Ref.* holder object allocated on the heap, which is an allocation cost worth knowing about on hot paths.
Inside list.forEach { ... }, you want to stop processing the current element and move to the next without exiting the surrounding function. What is the idiomatic way?
- Use continue to jump to the next loop iteration here
- Use break to stop the whole loop and exit immediately
- Use return@forEach to skip just the current element
- Use a bare return to leave the enclosing function now
Answer: Use return@forEach to skip just the current element
return@forEach is a labeled local return that ends only the current lambda invocation (the current element); break/continue are not allowed directly inside a lambda, and a bare return would exit the enclosing function.
Given inline fun <reified T> create(): T, why can't the body simply call T() to instantiate T?
- Because inline functions are forbidden from ever returning any generic reified type
- Because reified type parameters are erased and only exposed as a Class<T> token
- Because constructing T() would first require adding the crossinline modifier here
- Because reification enables type checks and reflection, not a no-arg constructor
Answer: Because reification enables type checks and reflection, not a no-arg constructor
Reification makes the concrete type usable for is T, as T, and T::class, but the compiler still cannot assume any particular constructor exists, so direct T() is disallowed; you would reflect via something like T::class.createInstance().
What does the Kotlin compiler do when you mark a function inline but it has no function-type (lambda) parameters?
- It refuses to compile the function and reports an error
- It emits a warning that inlining offers little benefit
- It silently treats the function as if it were noinline
- It automatically adds a reified type parameter for you
Answer: It emits a warning that inlining offers little benefit
The main payoff of inline is removing lambda-object allocation, so with no lambda parameters the compiler warns that inlining brings negligible benefit; reified type parameters are the notable reason to inline such a function anyway.
On the JVM, a lambda passed to an ordinary (non-inline) function expecting a (Int) -> String is compiled to an instance of:
- An instance of java.lang.Runnable with a run() method
- A class that implements kotlin.Function1<Int, String>
- Just a static method call, with no lambda object allocated
- A freshly started Thread wrapping the lambda body here
Answer: A class that implements kotlin.Function1<Int, String>
Non-inlined function values are realized as objects implementing the matching FunctionN interface (here Function1); that per-call object allocation is exactly what marking the calling function inline eliminates.
In numbers.filter { it > 0 }, which statement about the syntax is correct?
- it is a keyword for the lambda receiver, not an implicit parameter
- The lambda must remain inside parentheses, so filter({ it > 0 }) is the only form
- filter needs a named argument before it can accept any lambda at all
- The last function-type argument can move outside, and it is implicit
Answer: The last function-type argument can move outside, and it is implicit
Trailing-lambda syntax lets a final function-type argument be written outside the parentheses (which can be dropped when it is the only argument), and a single-parameter lambda exposes that argument implicitly as it.