What is `lateinit var`, what are its restrictions, and how do you safely check whether it has been initialized?
answer
- var only, non-null, no primitives
- throws UninitializedPropertyAccessException if read early
- check with this::prop.isInitialized
- needs a backing field — no custom accessors/delegation
- lateinit = pushed in later; by lazy = computed on first read
basics
~10 slateinit var lets you declare a non-null property without setting it right away, promising to assign it before first use. Reading it too early throws an exception. You can check readiness with this::prop.isInitialized.
solid answer
~40 s`lateinit var` defers initialization of a non-null property to after the constructor, useful for dependency injection or test/`@Before` setup. Restrictions: it works only on `var` (not `val`); the type must be **non-null** and **not a primitive** (no `Int`/`Boolean` — they have no null sentinel); it can't have a custom getter/setter; and it must have a backing field (no computed/delegated). Accessing it before assignment throws `UninitializedPropertyAccessException`. To test readiness safely use the reference syntax `this::prop.isInitialized` (or `::prop.isInitialized` inside the class). Under the hood the field starts as `null` and the synthesized getter throws if it's still `null`. Prefer it over nullable `var x: T? = null` only when the value is genuinely guaranteed before use and you want to avoid `!!`/null checks everywhere.
code
kotlin · 13 linesclass Parser {
lateinit var input: String
fun parse(): Int {
check(::input.isInitialized) { "input not set" }
return input.toInt()
}
}
val p = Parser()
// p.parse() // throws UninitializedPropertyAccessException path via check
p.input = "42"
println(p.parse()) // 42go deeper
Knows lateinit var defers initialization and throws if read too early.
Lists the key restrictions (var, non-null, non-primitive) and knows the exception type.
Uses ::prop.isInitialized, explains the backing-field/null-sentinel mechanism, and contrasts with nullable and by lazy.
Sets team policy on lateinit vs nullable vs lazy, considering DI lifecycles, testability, and the runtime-vs-compile-time safety trade-off.
## What `lateinit` is for `lateinit var` declares a **non-null** property whose value is supplied *after* construction — e.g., injected by a framework, wired in `@BeforeEach`, or set in `onCreate`. It lets you avoid making the type nullable (`T?`) just because you can't set it in the constructor. ```kotlin class Service { lateinit var repo: Repository // assigned later by DI fun handle() = repo.findAll() // assumes repo was injected } ``` ## Restrictions (all enforced by the compiler/runtime) - Only on **`var`**, never `val` (a `val` must be definitely assigned, so it needs no deferral mechanism). - The type must be **non-nullable** — the implementation uses `null` as the "uninitialized" sentinel. - The type must **not be a primitive** (`Int`, `Long`, `Double`, `Boolean`, …) because primitives can't hold the null sentinel. Use a nullable primitive or a default value instead. - **No custom getter/setter** and it must have a real backing field — so no computed or delegated property. - It can be a top-level or local `lateinit var` too, not only a member. ## What happens if you read it too early The synthesized getter throws **`kotlin.UninitializedPropertyAccessException: lateinit property repo has not been initialized`**. There is no compile-time guarantee of assignment — that's the trade-off. ## Checking initialization safely Use the **bound property reference** with `.isInitialized`: ```kotlin if (this::repo.isInitialized) { repo.close() } ``` This is only accessible from inside the class (or a lexically enclosing scope) that owns the property. It's the safe alternative to a try/catch around the access. ## How it works under the hood The backing field is a normal nullable reference initialized to `null`. The generated getter checks for `null` and throws if uninitialized; otherwise returns the value. `isInitialized` simply tests that field against `null`. ## `lateinit var` vs nullable `var x: T? = null` - `lateinit`: callers see a **non-null** type, no `?.`/`!!` noise; cost is a runtime exception if you misuse it. - Nullable: the compiler forces you to handle `null` everywhere, which is safer when absence is a real, expected state. Choose `lateinit` only when the value is **guaranteed** present before any read, and `null` is not a meaningful value. ## `lateinit` vs `by lazy` `by lazy { ... }` is for `val`, computes the value **on first access** from a lambda, and is thread-safe by default — use it when the property can produce itself. `lateinit var` is for values **pushed in from outside**; it has no initializer.
- Why can't `lateinit` be applied to an `Int`?Primitives can't represent the `null` sentinel the implementation uses to mean 'uninitialized'. Use a nullable Int or a default value.
- When would you choose `by lazy` over `lateinit`?When the value can compute itself on first access (a `val`), and you want thread-safe one-time initialization, rather than having it pushed in externally.
saying these in an interview costs you the question
- Saying `lateinit val` is allowed
- Claiming it works on `Int`/`Boolean`
- Catching a generic Exception instead of using `isInitialized`
- Confusing `lateinit` (no initializer, var) with `by lazy` (initializer, val)
- Believing the compiler guarantees it's assigned before use