skip to content

What is `lateinit var` in Kotlin and what problem does it solve?

level: juniorimportance: must knowfreq 80%

answer

  1. Deferred init of non-null var
  2. var only, non-null only, no primitives
  3. Avoids making type nullable
  4. UninitializedPropertyAccessException on early read
  5. DI / Android onCreate / test @BeforeEach

basics

~20 s

lateinit var lets you declare a non-null variable without giving it a value right away. You promise to set it before you read it. It's used when a value isn't known at object creation but will be ready before first use.

solid answer

~40 s

`lateinit` is a modifier on a mutable `var` of a non-null reference type that tells the compiler you will assign it later, before any read. Without it, a non-null property must be initialized in the constructor or at declaration. It's typically used for dependency injection, framework callbacks (e.g. Android `onCreate`, JUnit `@BeforeEach`), or builder-style setup where the value isn't available at construction time. The compiler skips the null check it would normally enforce, so you avoid making the type nullable (`Type?`) just to defer assignment. If you read it before assigning, you get an `UninitializedPropertyAccessException`. You can guard with `::prop.isInitialized` to check assignment state.

code

kotlin · 11 lines
kotlin
class Greeter {
    lateinit var name: String

    fun setup(n: String) { name = n }
    fun greet() = "Hello, $name"   // throws if setup() not called first
}

val g = Greeter()
// g.greet()  // UninitializedPropertyAccessException
g.setup("Ada")
println(g.greet())  // Hello, Ada

go deeper

for a junior

Knows it defers initialization of a non-null var and that you must assign before reading.

for a middle

Explains why it beats a nullable type and lists the var/non-null/no-primitive constraints.

for a senior

Frames it as moving the non-null guarantee from compile time to runtime and names real use cases (DI, lifecycle, tests).

for a principal

Discusses design trade-offs of giving up compiler verification and when an alternative (lazy, constructor injection, nullable) is the better contract.

## What `lateinit` means Kotlin enforces null safety: a property with a non-null type (like `String`, not `String?`) must hold a value at all times. Normally that means you initialize it in the constructor or at the declaration site. But sometimes the real value isn't available yet at construction time — for example a field injected by Spring/Dagger, or a view assigned in Android's `onCreate`, or a test fixture set up in `@BeforeEach`. `lateinit` is a modifier that says: *"This non-null `var` will be assigned later, before anyone reads it — trust me, compiler."* ```kotlin class UserService { lateinit var repository: UserRepository // no value yet fun init(repo: UserRepository) { repository = repo // assigned later } fun findUser(id: Long) = repository.find(id) // safe if init() ran first } ``` ## The alternative it replaces Without `lateinit`, you'd either: - Make it nullable: `var repository: UserRepository? = null` — then every use needs `?.` or `!!`, polluting the code with null handling for something you know is non-null after setup. - Initialize with a dummy/default value — often impossible or wasteful. `lateinit` keeps the type non-null and avoids both. ## Key constraints - Only on `var` (mutable), never `val`. - Only on **non-null** types (`String`, not `String?`). - **Not** allowed on primitive types like `Int`, `Boolean`, `Double` (use `by Delegates.notNull()` instead). - No custom getter/setter. - Can be a member property, top-level property, or local variable. ## What happens on early access Reading before assignment throws `UninitializedPropertyAccessException` ("lateinit property X has not been initialized"). You can defensively check with the reference syntax `::repository.isInitialized`. ## Mental model `lateinit` is a **deferred, unchecked promise** of non-null. The benefit is clean non-null types; the cost is that the compiler can no longer guarantee initialization — that responsibility moves to you, enforced at runtime instead of compile time.

  • Why not just use a nullable type instead?
    A nullable type forces null handling (`?.`, `!!`) on every access even though the value is logically always present after setup. `lateinit` keeps the type non-null so call sites stay clean.
  • Can `lateinit` be applied to a `val`?
    No. `lateinit` requires a `var` because assignment happens after declaration; a `val` can only be assigned once and must be initialized at its single assignment point.

Like reserving a parking spot with your name on it before your car arrives — the slot exists and is non-null, but trying to drive away before the car shows up fails.

saying these in an interview costs you the question

  • Claiming `lateinit` works on `val`
  • Saying it works on primitives like `Int`
  • Thinking it gives the property a default value (it doesn't)
  • Confusing it with `lazy` (which auto-initializes on first read)
  • Believing early access returns null instead of throwing

context