Trace the initialization order when a primary constructor with val/var parameters runs alongside property initializers and init blocks. What pitfalls arise?
answer
- Params bound first, then top-to-bottom body
- Initializers + init blocks interleave in source order
- Forward reference = uninitialized value
- Open member in init → subclass override is null
- Constructor params always safe to use
basics
~20 sFirst the constructor parameters get their values. Then property initializers and init blocks run from top to bottom in the order they appear. Using a property before it is set in that order can give you a null or wrong value.
solid answer
~40 sWhen an object is built, the primary constructor's parameters (including `val`/`var` property params) are bound first. Then **property initializers and `init { }` blocks execute interleaved in source order**, top to bottom. So a property declared later is not yet initialized when an earlier `init` block runs — referencing it forfeits its value (for non-null types this is a compile-time error or, with `lateinit`/nullable trickery, a runtime hazard). The primary-constructor property params are available throughout because they are bound before any initializer. A classic pitfall is open/overridable properties read during construction in a base class: the subclass override hasn't initialized yet, yielding a default/null. Keep init logic minimal, avoid calling open members from constructors/init, and order declarations so each depends only on what's above it.
code
kotlin · 8 linesclass Account(val owner: String, initial: Long) {
var balance = initial // runs after params bound
init {
require(initial >= 0) { "negative start" }
println("$owner opened with $balance")
}
val label = "$owner:$balance" // runs after the init above
}go deeper
Knows init blocks run during object creation.
States params-first then top-to-bottom interleaving of initializers and init blocks.
Explains forward-reference hazards and the open-member-in-constructor null trap across inheritance.
Sets construction conventions to keep init side-effect-free, validates invariants early, and avoids virtual dispatch during construction in a shared base hierarchy.
## The ordering rules 1. The **primary constructor parameters** are evaluated and bound (this includes `val`/`var` property parameters — their backing fields are assigned from the arguments). 2. **Property initializers** and **`init { }` blocks** then run **in the exact order they appear** in the class body, interleaved. ```kotlin class Demo(val a: Int) { val b = a + 1 // 2nd: runs first among body members init { println("$a $b") } // 3rd val c = b + 1 // 4th init { println(c) } // 5th } ``` Because `a` is a constructor property param, it is ready before any body member runs. `b`, `c`, and `init` blocks run strictly top to bottom. ## Forward-reference pitfall Reading a property that is declared **below** the current point means reading it **before** it is initialized: ```kotlin class Bad { init { println(x.length) } // x not initialized yet val x = "hi" } ``` For non-null `val`/`var` with declared-later initializers, the compiler usually rejects this. With `lateinit var` or nullable types you instead get a runtime `UninitializedPropertyAccessException` or a `null`. ## The open-member-in-constructor trap Calling or reading an **open** (overridable) member during base-class construction is dangerous: ```kotlin open class Base { open val tag: String = "base" init { println(tag) } // prints null when subclass overrides } class Sub : Base() { override val tag: String = "sub" } Sub() // prints null — Sub.tag not yet initialized ``` Order across inheritance: Base's constructor/init runs **before** Sub's property initializers, so the overridden `tag` is still its default (`null`). ## Why primary-constructor params are safe to use They are bound in step 1, before any initializer or `init` block, so referencing a `val`/`var` constructor parameter in any initializer or `init` block is always valid. ## Best practices - Declare properties in dependency order (each uses only earlier ones). - Keep `init` blocks small; prefer `val x = compute()` over an `init` that assigns. - Never call open members from a constructor or `init` block. - Use `require`/`check` in `init` for invariant validation.
- Why does reading an open property in a base-class init block return null?The base constructor runs before the subclass initializes its overriding property, so the override still holds its default (null), not the intended value.
- Can you reference a val/var constructor parameter inside an init block?Yes, always — constructor parameters are bound before any property initializer or init block executes.
saying these in an interview costs you the question
- Saying init blocks run before constructor parameters are bound
- Claiming declaration order doesn't affect initialization
- Not recognizing the open-member-in-constructor null trap
- Thinking all init blocks run after all property initializers
- Believing forward references just work at runtime