skip to content

Trace the initialization order when a primary constructor with val/var parameters runs alongside property initializers and init blocks. What pitfalls arise?

level: seniorimportance: should knowfreq 45%

answer

  1. Params bound first, then top-to-bottom body
  2. Initializers + init blocks interleave in source order
  3. Forward reference = uninitialized value
  4. Open member in init → subclass override is null
  5. Constructor params always safe to use

basics

~20 s

First 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 s

When 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 lines
kotlin
class 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

for a junior

Knows init blocks run during object creation.

for a middle

States params-first then top-to-bottom interleaving of initializers and init blocks.

for a senior

Explains forward-reference hazards and the open-member-in-constructor null trap across inheritance.

for a principal

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

context