skip to content

Inlining isn't free. Describe concrete situations where marking a function `inline` hurts more than it helps.

level: middleimportance: must knowfreq 60%

answer

  1. Cost = bytecode duplicated per call site
  2. Big body x many call sites = bloat
  3. No lambda param -> compiler warns
  4. Public inline freezes impl into consumers
  5. 64KB method limit + JIT thresholds

basics

~20 s

Inlining copies the whole function body into every place it's called. If the body is big or it's called from many places, your bytecode balloons. And if the function has no lambda parameter, you gain almost nothing.

solid answer

~50 s

The cost of `inline` is **code duplication**: the function body plus its lambda bodies are emitted at every call site. This hurts when (1) the function body is large — a 100-line `inline` function called in 50 places multiplies that code 50×, hurting instruction-cache locality and method-size limits; (2) the function has no functional parameters, so there's no lambda allocation to save and the compiler warns the gain is insignificant; (3) the function is part of a public library API, because inlined code is baked into every consumer's binary, freezing implementation details and breaking binary compatibility if you change them. Inlining can also defeat JIT optimizations that work better on a single shared method, and it can grow the method beyond the JVM's ~64KB bytecode limit or past the JIT's inlining thresholds. Rule of thumb: inline small functions whose whole point is taking a lambda; don't inline large or lambda-free functions.

code

kotlin · 8 lines
kotlin
// Compiler warning: "Expected performance impact from inlining is insignificant.
// Inlining works best for functions with lambda parameters"
inline fun square(x: Int): Int = x * x   // no lambda -> remove `inline`

// Genuinely worth inlining: small, takes a lambda, used in hot paths
inline fun <T> Iterable<T>.fastForEach(action: (T) -> Unit) {
    for (e in this) action(e)
}

go deeper

for a junior

Knows inlining duplicates code and that's the basic downside.

for a middle

Lists concrete bad cases: large bodies, no lambda param, many call sites, and the bloat consequence.

for a senior

Adds 64KB limit, JIT interaction, and the public-API/binary-compatibility angle.

for a principal

Frames a policy: measure first, restrict inline to small lambda helpers, and treats inline as part of a library's compatibility contract.

## The core cost: duplicated bytecode `inline` substitutes the function's body — and the bodies of its lambda arguments — at every call site instead of emitting one shared method. So the saved lambda allocation comes at the price of **code size that scales with the number of call sites**. ## When inlining hurts ### 1. Large function bodies If an `inline` function is long and called from many sites, the duplicated code: - bloats the final artifact (`.class`/APK/jar) size; - worsens **instruction-cache** locality (more distinct code paths to keep hot); - can push a method past the JVM's hard **~64KB per-method bytecode limit**, which is a hard compile error. ### 2. No functional parameters The whole benefit is removing lambda allocation and `invoke()` dispatch. A function with no lambda parameter gains nothing from being inline, so the compiler emits a warning suggesting you remove `inline` — you'd just be duplicating bytecode. ### 3. Public library API surface Inlined bodies are compiled into every *consumer's* binary. That means: - changing the implementation does **not** take effect for already-compiled callers until they recompile; - you can break **binary compatibility** by altering an inline body or what private members it touches; - it forces use of `@PublishedApi` to expose internals the inline body references. ### 4. Fighting the JIT The HotSpot JIT already inlines small hot methods at runtime, often devirtualizing `invoke()` too. Source-level `inline` removes the *shared* method the JIT likes to profile and optimize, and oversized inlined methods can exceed JIT inlining thresholds, ending up *slower*. ## Practical rule ```kotlin // Good: tiny, lambda-driven, no captured state inline fun <T> T.alsoLog(block: (T) -> Unit): T { block(this); return this } // Bad: big body, no lambda -> warning, pure bloat inline fun parseHugeConfig(text: String): Config { /* 80 lines */ } ``` Inline small higher-order helpers; measure before inlining anything large; be extra cautious with anything `public` in a library.

  • What hard limit can over-aggressive inlining hit on the JVM?
    The 64KB per-method bytecode size limit. If inlined code grows a method past it, compilation fails outright.
  • How does inlining interact with the HotSpot JIT?
    The JIT already inlines hot small methods and can devirtualize invoke(). Over-large source-level inlining can exceed JIT inlining limits and prevent those optimizations, sometimes making code slower.

Inlining is like photocopying a long memo into every folder instead of keeping one master copy — fine for a sticky note, wasteful for a 50-page report.

saying these in an interview costs you the question

  • Believing inline is always a performance win
  • Marking large functions inline 'for speed'
  • Ignoring code-size / binary-size consequences
  • Not knowing the no-lambda warning exists
  • Unaware public inline leaks impl into callers

context