skip to content

When should you initialize a `val` directly in its declaration versus assigning it inside an `init` block? What can each do that the other cannot?

level: middleimportance: should knowfreq 40%

answer

  1. Single expression -> direct initializer
  2. Statements/validation/branching -> assign in init
  3. Uninitialized val assigned exactly once in init
  4. Definite assignment preserves immutability
  5. Timing identical; only file position differs

basics

~10 s

Use a direct initializer for simple values computed from one expression. Use an init block when assignment needs several statements, validation, or branching before you can decide the value.

solid answer

~40 s

A direct property initializer (`val x = expr`) is best for a value that's a single expression. An `init` block assignment is needed when the value requires **multiple statements, validation, branching (`if`/`when`), try/catch, or loops** before settling. Both can read primary-constructor parameters and both run in the same top-to-bottom sequence, so they're equivalent in *timing*. A `val` with **no** initializer in its declaration can be assigned **exactly once** inside an `init` block (definite assignment), which is the idiomatic way to compute a complex immutable value. You cannot, however, split a single property's assignment across two init blocks. Choose the form that keeps the property's computation readable and its immutability intact.

code

kotlin · 7 lines
kotlin
class Range(from: Int, to: Int) {
    val ordered: Pair<Int, Int>   // no initializer here
    init {
        require(from != to)
        ordered = if (from < to) from to to else to to from  // assigned once
    }
}

go deeper

for a junior

Knows you can either write val x = ... or assign x inside init.

for a middle

Chooses correctly between direct initializer and init assignment based on complexity and uses definite assignment for vals.

for a senior

Articulates immutability guarantees, single-assignment rule, and when lazy is preferable.

for a principal

Weighs construction cost, testability, and exception-safety when deciding where initialization logic belongs.

## Two ways to initialize a property ### Direct initializer ```kotlin class C(seed: Int) { val doubled = seed * 2 // single expression } ``` Great for one-expression values. Concise and obviously a `val`. ### Assignment inside `init` Declare the property **without** a value, then assign it once in `init`: ```kotlin class Parser(raw: String) { val tokens: List<String> init { val trimmed = raw.trim() require(trimmed.isNotEmpty()) tokens = if (trimmed.contains(',')) trimmed.split(',') else listOf(trimmed) } } ``` This is the idiom when you need **statements, validation, or branching** to produce the value. The compiler's **definite-assignment analysis** guarantees a `val` is assigned exactly once on every path, so immutability is preserved even though the declaration has no `= ...`. ## What each form can / can't do - **Direct initializer:** single expression only; you can still embed `run { }`/`let { }` for a few lines, but multi-statement logic with validation reads poorly. - **init assignment:** any control flow; but a `val` can be assigned in **only one** place across all init blocks — you cannot conditionally assign it in two separate init blocks. - **Timing is identical:** both participate in the same declaration-order sequence, so neither runs 'before' the other by virtue of its form — only its position in the file matters. ## Guidance - One clean expression -> direct initializer. - Needs `require`/`check`, branching, loops, or intermediate locals -> declare `val name: T` and assign in `init`. - Heavy/expensive and possibly unused -> consider `by lazy {}` instead, which defers computation to first access.

  • Can a `val` be assigned in two different init blocks depending on a condition?
    No. Definite-assignment requires exactly one assignment on every path; conditional logic must live inside one assignment expression or a single block.
  • When would `by lazy` beat both forms?
    When the value is expensive and may never be used — lazy defers computation to first access instead of running at construction.

saying these in an interview costs you the question

  • Claiming init-assigned vals break immutability
  • Thinking direct initializers run before all init blocks regardless of position
  • Splitting one val's assignment across multiple init blocks
  • Using var + reassignment where a single init assignment suffices
  • Putting heavy expensive work in init when lazy fits better

context