What does the compiler actually generate for nested vs inner classes, and how should that inform your default choice when designing an API?
answer
- Nested -> JVM static class
- Inner -> synthetic this$0 outer field
- Outer passed as hidden constructor arg
- Default nested, escalate to inner
- Inner couples lifetime to outer instance
basics
~20 sA nested class compiles to a standalone (static-like) class with no link to the outer. An inner class compiles to a class with a hidden field pointing at the outer object. Default to nested and only add inner when you truly need that link.
solid answer
~40 sOn the JVM, a Kotlin nested class compiles to a `static` nested class with no synthetic outer field — it stands alone. An `inner` class compiles to a non-static nested class carrying a synthetic field (conventionally `this$0`) that holds the enclosing instance and is passed to its constructor; that is what powers `this@Outer`. Design-wise this means: (1) nested is cheaper (no extra field, no instance coupling) and safer (no implicit retention); (2) inner couples each instance's lifetime to an outer instance, which is exactly what you want for iterators/builders/views but a liability for callbacks. The principled default is **nested**, escalating to `inner` only when the type's contract genuinely requires live outer state. For helpers that need only a few fields, prefer nested + explicit parameters so retention is visible at the call site.
code
kotlin · 13 linesclass Registry<T> {
private val data = mutableListOf<T>()
fun add(x: T) { data += x }
// inner: needs live outer state + T -> justified
inner class Cursor : Iterator<T> {
private var i = 0
override fun hasNext() = i < data.size
override fun next() = data[i++]
}
// nested: pure helper, no outer state -> default
data class Stats(val size: Int)
fun stats() = Stats(data.size)
}go deeper
Knows inner keeps an outer reference and nested does not, even if unsure of bytecode names.
Can state that inner stores the outer in a hidden field and nested compiles to a static-like class.
Explains the synthetic this$0 constructor injection and applies a nested-by-default rule with concrete exceptions.
Frames the choice as an API-design and lifetime-ownership decision, balancing cost, retention, ergonomics, and generics across a codebase, and codifies a team default.
## What the compiler emits - **Nested** (`class Outer { class N }`): a JVM `static` nested class. No synthetic field, no outer parameter in its constructor. Behaves like any top-level class that merely lives in the `Outer` namespace. - **Inner** (`inner class I`): a JVM non-static nested class. The compiler adds a **synthetic field** (the historical name is `this$0`) initialized from a hidden constructor parameter that receives the enclosing instance. `this@Outer` reads that field. This is why you must construct it from an instance (`outer.I()`): the outer reference is a constructor argument under the hood. ```kotlin class Outer { inner class I // ~ class I(private val this$0: Outer) class N // ~ static class N } ``` ## Design implications 1. **Cost & coupling**: inner adds one reference field per instance and binds its lifetime to an outer instance. Nested has neither. 2. **Retention/leaks**: the synthetic outer field is a strong reference — see the leak discussion. Nested cannot leak the outer because there is nothing to leak. 3. **Construction ergonomics**: inner requires an outer instance; nested is constructible standalone, which is friendlier for factories and tests. 4. **Generics/state access**: only inner sees the outer's instance state and per-instance type parameters. ## A practical decision rule - Default to **nested**. - Use **inner** only when the type's job is to operate on a specific outer instance's live state (custom `Iterator`, `ListIterator`, builder/DSL receiver, a mutable view). - If a nested helper needs a little outer data, pass it explicitly (a snapshot, an id) so retention is visible and bounded. - Reserve `companion object` for the single per-class singleton (factories, constants), not for per-instance helpers. ## Key takeaways - Nested → static class, no outer field; inner → synthetic `this$0` outer field. - Inner trades a reference + lifetime coupling for live outer access. - Principled default: nested; escalate to inner only on genuine need.
- What is the synthetic field that backs `this@Outer` historically called on the JVM?`this$0` — a synthetic field holding the enclosing instance, populated from a hidden constructor parameter.
- Give one case where escalating from nested to inner is clearly justified.A custom `Iterator`/`ListIterator` or mutable view that must read the outer instance's live backing data and its type parameter `T`.
saying these in an interview costs you the question
- Claiming nested classes also get a synthetic outer field
- Saying inner and nested compile to identical bytecode
- Defaulting to `inner` without a state/lifetime justification
- Thinking the outer reference is set via reflection rather than a constructor arg
- Treating `companion object` as a substitute for a per-instance inner helper