What are the initialization rules for val? Can a val be assigned later, conditionally, or remain uninitialized?
answer
- val = assign-once, not assign-at-declaration
- Definite assignment: one assignment on every path before read
- Deferred val usually needs an explicit type
- val property can be set in init{} or constructor
- Lazy compute = by lazy; lateinit needs var
basics
~20 sA val must be set exactly once before it is read. You can split the declaration and the assignment, and even assign it in different branches of an if, as long as every path sets it once and you never read it before then.
solid answer
~50 sA `val` is *assign-once*, not *assign-at-declaration*. The compiler enforces **definite assignment**: a `val` must be initialized exactly once on every path before its first read, and never reassigned. This permits deferred and conditional initialization: ```kotlin val x: Int if (cond) x = 1 else x = 2 // each branch assigns once ``` The type usually must be declared explicitly when you defer initialization, since there's no initializer to infer from. A `val` property in a class can be initialized in an `init` block or constructor rather than at the declaration. What you cannot do: read it before assignment, assign it twice, or leave a path where it's unset. For lazily computed read-only values use `by lazy {}`; for non-null properties initialized after construction (e.g., DI) use `lateinit var` (a sibling topic) — note `lateinit` requires `var`, not `val`.
code
kotlin · 9 linesfun classify(n: Int): String {
val kind: String // explicit type, deferred
when {
n < 0 -> kind = "negative"
n == 0 -> kind = "zero"
else -> kind = "positive"
}
return kind // every branch assigned exactly once
}go deeper
Knows a val can be assigned once and roughly where.
Explains definite assignment and conditional/deferred initialization with explicit types.
Distinguishes deferred val, by lazy, and lateinit var and picks the right tool per scenario.
Reasons about initialization order, init-block hazards, and immutability guarantees these choices provide across an API.
## Assign-once ≠ assign-at-declaration A common misconception is that `val` must be initialized on the same line. It doesn't — it must be initialized **exactly once before first use**. The compiler performs **definite-assignment analysis**. ```kotlin val message: String // declared, not yet assigned message = computeMessage() // assigned once — OK println(message) ``` Because there's no initializer to infer from, you typically must **declare the type explicitly** in the deferred case. ## Conditional initialization As long as **every** code path assigns the `val` exactly once before any read, branches are fine: ```kotlin val grade: Char val score = readScore() grade = if (score >= 90) 'A' else if (score >= 80) 'B' else 'C' ``` Or with `if/else` statements: ```kotlin val label: String if (flag) { label = "on" } else { label = "off" } ``` What the compiler rejects: - Reading before assignment → "Variable must be initialized". - Assigning twice → "Val cannot be reassigned". - A path that leaves it unassigned, then reading it. ## Properties: init blocks and constructors A `val` **property** can be initialized at the declaration, in an `init {}` block, or from a primary/secondary constructor parameter — but exactly once: ```kotlin class Config(raw: String) { val parsed: Map<String, String> init { parsed = raw.split(";").associate { it.split("=").let { (k, v) -> k to v } } } } ``` ## Related tools (siblings, for contrast) - **`by lazy {}`** — a `val` whose initializer runs on first access and caches the result (great for expensive computations): ```kotlin val heavy: Heavy by lazy { buildHeavy() } ``` - **`lateinit var`** — for non-null properties set after construction (DI/tests). It requires `var`, not `val`, and throws `UninitializedPropertyAccessException` if read first. (Covered fully in the lateinit leaf.) ## Summary `val` gives you flexibility: defer it, branch it, compute it in `init` — the single rule is *exactly one assignment on every path, before any read*.
- Why must you usually annotate the type for a deferred val?There's no initializer expression at the declaration, so the compiler has nothing to infer the type from.
- What's the difference between a deferred val and lateinit var?A deferred val is still assign-once and checked by definite-assignment within a scope; lateinit var is reassignable, for non-null properties set after construction, and throws if read before init.
saying these in an interview costs you the question
- Claiming a val must be initialized on its declaration line
- Thinking you can assign a val in two branches AND fall through unassigned
- Confusing deferred val with lateinit (lateinit needs var)
- Believing by lazy works on a var
- Not realizing reading a deferred val before assignment is a compile error