What happens if an `init` block (or property initializer) reads a property declared later in the class? Show the failure mode.
answer
- Forward reference = read before init runs
- Direct read -> compile error
- Indirect via open override -> JVM default (0/null)
- Calling open members from init leaks this
- lateinit/lazy as safer alternatives
basics
~20 sReading a property before its initializer has run gives you trouble: usually the compiler rejects it, but in some cases the code runs and you see a default value like 0 or null instead of the real value.
solid answer
~40 sBecause initialization is strictly top-to-bottom, reading a property *above* its initializer is a **forward reference**. For a direct read, Kotlin's compiler reports an error like 'Variable cannot be accessed before declaration'. But the protection isn't total: when the read happens **indirectly** — e.g. via an open member overridden in a subclass, or through a method called from `init` that touches a not-yet-initialized property — the compiler can't see it, and at runtime you observe the JVM **default value** (`0`, `false`, `null`) rather than the intended one. For non-null reference types this can surface as a `NullPointerException` despite the type being declared non-null, because the backing field is still `null` mid-construction. The cure is to order declarations so every member is initialized before any code reads it, and avoid calling overridable methods from `init`.
code
kotlin · 8 linesopen class Base {
open val tag: String = "base"
init { check(tag.isNotEmpty()) } // dispatches to override -> tag is null here!
}
class Derived : Base() {
override val tag: String = "derived"
}
// new Derived() -> NPE inside Base.init because Derived.tag backing field is still nullgo deeper
Recognizes that reading a later property in init is wrong and often won't compile.
Explains the compile-time error for direct reads and knows ordering must respect dependencies.
Explains the indirect-read default-value/NPE trap via open overrides during base construction and avoids leaking this.
Designs class hierarchies and uses lazy/lateinit/factory patterns to eliminate construction-order hazards across modules.
## Forward references during construction Initialization runs in declaration order. A **forward reference** is reading a member before its initializer has executed. ### Case 1 — direct read: compile error ```kotlin class A(x: Int) { init { println(y) } // error: variable 'y' must be initialized val y = x + 1 } ``` The compiler performs definite-assignment analysis and rejects the obvious case. ### Case 2 — indirect read via an open/overridden member: default value at runtime This is the dangerous one. When a base class calls an **open** member from its `init`/initializer, and a subclass overrides it, the override runs **while the subclass's own fields are still uninitialized**: ```kotlin open class Base { open val size: Int = 0 init { println("Base sees size=$size") } } class Derived : Base() { override val size: Int = 42 } // new Derived() prints "Base sees size=0" (NOT 42) ``` Why: the JVM constructs `Base` first, so `Base.init` runs before `Derived`'s `size = 42` initializer. The overridden `size` getter is dispatched dynamically to `Derived`, whose backing field is still the JVM default `0`. For a non-null `String`, you'd read `null`, risking a `NullPointerException` even though the type is non-nullable. ## Rules to stay safe - Declare members **above** every place that reads them. - **Never call open/abstract members from `init` or property initializers** — IntelliJ/`detekt` warn about "leaking `this` in constructor". - Prefer `lazy {}` or computing in `init` after dependencies are set, rather than relying on construction order across the inheritance boundary. ## Tools - `lateinit var` defers initialization but throws `UninitializedPropertyException` if read early — a clearer failure than a silent default. - `by lazy {}` computes on first access, sidestepping construction-order issues entirely (but not usable for `var` and runs outside the constructor).
- Why does the override return a default instead of the real value?The base constructor runs before the subclass initializes its fields; the dynamically dispatched override reads its still-default backing field.
- How does `lateinit` change the failure mode?Reading a lateinit property before assignment throws UninitializedPropertyAccessException — a loud, explicit failure instead of a silent default.
Asking a not-yet-hired employee to do a task: directly, HR blocks it; through a temp agency, someone shows up but with no training — the default, useless answer.
saying these in an interview costs you the question
- Claiming the compiler always prevents forward references
- Not knowing open members dispatch to the subclass during base construction
- Saying a non-null type can never be null
- Calling overridable methods from init without concern
- Confusing this with thread-safety rather than construction order