skip to content

You're designing a higher-order API. How do you decide between making a function `inline` with a `noinline` parameter versus just writing a plain function?

level: seniorimportance: nice to knowfreq 22%

answer

  1. Inline only if a lambda benefits (hot / return / reified)
  2. noinline the lambda you must store
  3. All-noinline => write a plain function
  4. Inline duplicates bytecode per call site
  5. Library hot paths justify inline; app code rarely does

basics

~20 s

Make it inline only if at least one lambda benefits from inlining (hot calls, non-local returns, or reified types). Use noinline for the one lambda you must keep as an object. If every lambda needs to be an object, just write a normal function.

solid answer

~40 s

Inlining pays off when a lambda is called frequently (avoiding allocation/dispatch), or when you need a feature only inline gives you: non-local returns from the lambda, `reified` type parameters, or zero-overhead DSL helpers. If at least one parameter benefits, mark the function `inline` and tag the lambda(s) you need as values `noinline`. But inlining duplicates the body's bytecode at every call site, so it's wrong for large bodies or rarely-hot paths. The decisive question: 'Does any lambda benefit from being inlined?' If yes => inline + noinline the stored ones. If every lambda must be an object (none benefits), there's nothing to inline — drop `inline` and write a plain function, avoiding the redundant-noinline warning and bytecode bloat. Library hot-paths (collection ops, `use`, `synchronized`) justify inline; ordinary application code usually doesn't.

code

kotlin · 5 lines
kotlin
// Mixed: `body` benefits from inlining, `cleanup` is stored.
inline fun guarded(body: () -> Unit, noinline cleanup: () -> Unit) {
    registry.add(cleanup) // stored -> noinline
    body()                // hot -> inlined
}

go deeper

for a junior

Knows inline is for performance and noinline keeps a lambda as an object.

for a middle

Lists concrete inline benefits (hot calls, reified, non-local return) and uses noinline for stored lambdas.

for a senior

Articulates the full tradeoff including bytecode duplication and the all-noinline 'just use a plain function' rule.

for a principal

Sets a team/library policy: where inlining is justified, binary-compatibility implications of public inline APIs, and instruction-cache/codegen costs at scale.

## The decision framework Start from what `inline` actually buys you, then check whether any parameter needs it. ### Reasons to make a function `inline` 1. **Hot lambda** — called often; inlining removes per-call `FunctionN` allocation and virtual `invoke` (e.g. `map`, `filter`, `forEach`). 2. **Non-local returns** — you want `return` inside the lambda to exit the caller (only inlined lambdas allow this). 3. **`reified` type parameters** — only inline functions can have them (`inline fun <reified T> ...`). 4. **Low-overhead control-flow helpers / DSLs** — `run`, `let`, `with`, `use`, `synchronized` are inline to be free. ### Cost of `inline` - The function body is **duplicated** into every call site => larger bytecode, possible instruction-cache pressure for big bodies. - Public inline functions leak implementation into call sites (binary-compatibility care). ## Where `noinline` fits If you've decided to inline (because at least one lambda benefits), but one lambda must be **stored / returned / passed to a non-inline function**, tag exactly that parameter `noinline`. You keep the benefit for the others and pay one object for the stored one. ```kotlin // Good: inline pays off for `action` (hot, non-local return); // `onError` is stored, so it's noinline. inline fun retrying( action: () -> Unit, noinline onError: (Throwable) -> Unit ) { try { action() } catch (e: Throwable) { errorHandlers.add(onError); throw e } } ``` ## When to NOT inline If **every** lambda parameter would be `noinline` — i.e. none is called in a way that benefits — then inlining buys nothing but bytecode bloat, and the compiler warns that the `inline` is effectively pointless. Write a **plain function** instead: ```kotlin // All lambdas are just stored -> no inline benefit -> plain function fun register(onStart: () -> Unit, onStop: () -> Unit) { starts.add(onStart); stops.add(onStop) } ``` ## Rule of thumb - Some lambda benefits from inlining => `inline`, with `noinline` on the value-typed ones. - No lambda benefits => plain function. - Reserve aggressive inlining for small, hot, library-grade helpers — not every app-level higher-order function.

  • What does the Kotlin compiler tell you if an inline function gains no benefit from inlining?
    It warns that the function's `inline` modifier has no real effect (e.g. when all lambdas are noinline / nothing is inlined), suggesting you drop `inline`.
  • Why are stdlib functions like `let` and `use` inline?
    They are tiny, ubiquitous control-flow helpers; inlining makes them zero-overhead and lets blocks use non-local returns and (for some) reified types.

saying these in an interview costs you the question

  • Marking everything inline reflexively for 'speed'
  • Inlining large function bodies, bloating bytecode
  • Keeping inline when every lambda is noinline
  • Ignoring binary-compatibility risks of public inline funcs
  • Not citing reified/non-local-return as inline-only motivations

context