What exactly happens when you read a `lateinit var` before assigning it, and how does this differ from a nullable property?
answer
- Early read -> UninitializedPropertyAccessException
- Subclass of RuntimeException, message names the prop
- Fail-fast at access site, not silent default
- Nullable returns null instead of throwing
- lateinit = always present; nullable = maybe absent
basics
~20 sReading a lateinit var before it's set throws an UninitializedPropertyAccessException with a message naming the property. A nullable property instead returns null, which you can handle with ?. or ?: — it never throws on read.
solid answer
~40 sOn early read of an unassigned `lateinit` property, the compiler-generated getter detects the internal null sentinel and throws `UninitializedPropertyAccessException` (a subclass of `RuntimeException`), with a message like 'lateinit property X has not been initialized'. This is fail-fast: it surfaces the bug at the exact access site rather than letting a default value silently flow through. A nullable property (`Type?`) behaves completely differently — it legitimately holds `null` and returns it; you handle absence with `?.`, `?:`, `let`, or `!!`. The trade-off: `lateinit` keeps non-null types and crashes loudly if your setup ordering is wrong; nullable models genuine optionality but forces null handling everywhere. `lateinit` is for 'definitely present after setup', nullable is for 'may legitimately be absent'.
code
kotlin · 7 linesclass A { lateinit var s: String }
class B { var s: String? = null }
fun demo() {
// A().s.length // throws UninitializedPropertyAccessException
println(B().s?.length) // prints null, no throw
}go deeper
Knows reading before assignment throws an exception rather than returning null.
Names UninitializedPropertyAccessException and contrasts it cleanly with nullable behavior.
Frames the throw as intentional fail-fast and explains when lateinit vs nullable is the right contract.
Argues that swapping a runtime guarantee for a compile-time one is a deliberate design trade and avoids exception-driven control flow.
## The exception ```kotlin class Svc { lateinit var repo: Repo fun run() = repo.query() // if repo unset... } Svc().run() // kotlin.UninitializedPropertyAccessException: // lateinit property repo has not been initialized ``` `UninitializedPropertyAccessException` extends `RuntimeException` (unchecked). The generated getter checks the backing field against an internal not-set marker and throws if it hasn't been assigned. The message names the property, which makes diagnosis quick. ## Why fail-fast is the point `lateinit` deliberately turns a missing-initialization mistake into an immediate, loud crash at the access site. Compare with a property defaulted to a dummy value: that would hide the ordering bug and corrupt behavior silently. The exception is a feature — it pins the error to the real cause. ## Contrast with nullable (`Type?`) | Aspect | `lateinit var x: T` | `var x: T? = null` | |--------|---------------------|--------------------| | Type at call sites | non-null `T` | nullable `T?` | | Read before set | throws `UninitializedPropertyAccessException` | returns `null` | | Handling needed | none (assume present) | `?.`, `?:`, `let`, `!!` | | Intent | always present after setup | may be genuinely absent | | Primitives | not allowed | allowed (`Int?`) | ## Choosing between them - Use **`lateinit`** when the value is logically required and will exist before any use, but can't be set in the constructor (DI, lifecycle, tests). You accept a runtime crash if setup ordering is wrong. - Use **nullable** when absence is a valid state you must handle gracefully. ## Defensive checking If you genuinely can't guarantee order, guard with `::x.isInitialized` rather than catching the exception — exceptions for control flow are an anti-pattern.
- Is `UninitializedPropertyAccessException` checked or unchecked?Unchecked — it extends `RuntimeException`, so the compiler doesn't force you to declare or catch it.
- Should you catch the exception to handle unset state?No. Prefer `::prop.isInitialized` to test state; using exceptions for normal control flow is an anti-pattern and obscures real bugs.
saying these in an interview costs you the question
- Saying early read returns null instead of throwing
- Claiming the exception is checked
- Recommending try/catch over isInitialized for normal flow
- Not knowing the message names the property
- Conflating lateinit semantics with nullable semantics