skip to content

Why can you only call `isInitialized` through `this::prop` from inside the declaring class, and not on another object's lateinit property from outside?

level: middleimportance: should knowfreq 40%

answer

  1. Extension on bound KProperty0
  2. Reads the private backing field
  3. Declaring class + nested/inner only
  4. Encapsulation: lateinit is an impl detail
  5. Expose isReady() / nullable for outsiders

basics

~20 s

The check needs a direct reference to that exact property, which Kotlin only gives you where the property is visible — normally inside the class that declares it. From outside you usually can't form that reference, so you can't call the check.

solid answer

~40 s

`isInitialized` is declared as an extension on a **bound property reference** (`KProperty0`) of a backing field. To form `this::prop` the property must be in scope, so historically it was restricted to the declaring class because `lateinit` backing fields are private. As of Kotlin 1.3+, `isInitialized` is callable from a **lexically enclosing scope** that has access to the property's backing field — i.e. the declaring class and nested/inner classes within the same file/class body. You still cannot do `otherInstance::prop.isInitialized` from an unrelated class, because the bound reference would target a property whose backing field isn't accessible there, and the compiler rejects it. This keeps the check an implementation detail of the owning type rather than part of its public API. If callers outside need 'is it ready?', expose a method or a nullable property instead.

code

kotlin · 6 lines
kotlin
class Repo {
    private lateinit var cache: String
    fun isReady() = this::cache.isInitialized   // expose intent, not internals
    inner class V { fun ok() = this@Repo::cache.isInitialized }
}
// fun bad(r: Repo) = r::cache.isInitialized  // does not compile

go deeper

for a junior

Knows the check is used 'inside the class' but may not say why.

for a middle

Ties the limit to the private backing field and bound property reference; knows inner classes can reach the outer property.

for a senior

Frames it as encapsulation — initialization state is an implementation detail — and proposes isReady()/nullable as the public contract.

for a principal

Treats the scoping rule as a deliberate API-design guardrail and discusses how to model readiness in a stable public contract instead.

## The mechanism behind the restriction `isInitialized` is not a normal member. It is a synthetic `val` the compiler resolves on a **bound `KProperty0` reference** to a `lateinit` property: ```kotlin val kotlin.reflect.KProperty0<*>.isInitialized: Boolean ``` Under the hood it inspects the property's **backing field** to see whether it still holds the 'uninitialized' sentinel. ## Why scope matters `lateinit` properties compile to a **backing field** that is effectively part of the class's private state. To form the bound reference `this::prop` and call `isInitialized`, the compiler must be able to read that backing field. That access is granted only to: - The **declaring class** itself. - **Nested and inner classes** lexically inside it that can see the field. So `this::prop.isInitialized` works in a method of the owning class, but `repo::cache.isInitialized` from an unrelated class does **not** compile — the backing field isn't visible there. ```kotlin class Repo { lateinit var cache: String fun ready() = this::cache.isInitialized // OK: declaring class inner class View { fun ok() = this@Repo::cache.isInitialized // OK: inner class, field visible } } fun outside(r: Repo) { // r::cache.isInitialized // compile error: not in scope } ``` ## Why the language designers chose this - `lateinit` is an **implementation detail**. Whether something is initialized yet is internal state; leaking it as public API would couple callers to your storage strategy. - The reflective check reads a backing field directly — exposing that broadly would break encapsulation. ## What to do when outsiders need the info - Expose a method: `fun isReady(): Boolean = this::cache.isInitialized`. - Or model the value as **nullable** (`var cache: String? = null`) and let callers use `?.`/`!= null`. - Or use `by lazy { }` if there is no meaningful 'not yet' state to observe. ## Subtlety: same property only The reference passed to `isInitialized` must point at a `lateinit` property; passing a `KProperty0` of a non-lateinit property is a compile error, so you cannot smuggle the check onto arbitrary references.

  • Can an inner class check the outer class's lateinit property?
    Yes — via this@Outer::prop.isInitialized, since the inner class can see the outer backing field.
  • How should a caller outside the class learn whether the value is ready?
    Expose a method like isReady() that wraps the check, or model the field as nullable.

saying these in an interview costs you the question

  • Claiming you can call otherObject::prop.isInitialized from any class
  • Not connecting the limit to backing-field visibility/encapsulation
  • Thinking the restriction is arbitrary rather than about private state
  • Suggesting reflection hacks instead of exposing a method

context