skip to content

What is `lateinit var`, what are its restrictions, and how do you safely check whether it has been initialized?

level: seniorimportance: must knowfreq 70%

answer

  1. var only, non-null, no primitives
  2. throws UninitializedPropertyAccessException if read early
  3. check with this::prop.isInitialized
  4. needs a backing field — no custom accessors/delegation
  5. lateinit = pushed in later; by lazy = computed on first read

basics

~10 s

lateinit 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 lines
kotlin
class 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())        // 42

go deeper

for a junior

Knows lateinit var defers initialization and throws if read too early.

for a middle

Lists the key restrictions (var, non-null, non-primitive) and knows the exception type.

for a senior

Uses ::prop.isInitialized, explains the backing-field/null-sentinel mechanism, and contrasts with nullable and by lazy.

for a principal

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

context