What & How: lateinit & lazy in Kotlin

In Kotlin, all variables must be initialized before use — this ensures null safety and prevents runtime crashes. However, there are cases where initialization can’t happen immediately. To handle such scenarios, Kotlin provides two smart property initialization options: lateinit and lazy.

Why do we need lateinit and lazy?

Kotlin enforces initialization at declaration to ensure safety. But sometimes:

That’s where lateinit (for variables) and lazy (for read-only properties) come into play.


lateinit – Late Initialization for var

What is lateinit?

The lateinit keyword tells the compiler that you’ll initialize the variable later, but before using it. It’s mainly used for non-nullable mutable properties (var).

Syntax


lateinit var variableName: Type
  

Example


class Student {
    lateinit var name: String  // Declaration without initialization

    fun assignName() {
        name = "Talha Abbas"
    }

    fun printName() {
        if (this::name.isInitialized) {   // Check before using
            println("Student Name: $name")
        } else {
            println("Name not initialized yet.")
        }
    }
}

fun main() {
    val s = Student()
    s.printName()
    s.assignName()
    s.printName()
}
  

Here, name is initialized later using assignName(), but not at declaration. Accessing it before initialization will throw UninitializedPropertyAccessException.

Important Clarification

You might notice that lateinit works with String even though you can’t use it with primitives like Int or Boolean. That’s because in Kotlin, String is an object type (a reference type), not a primitive. Kotlin restricts lateinit to reference types only — it cannot be applied to primitive or nullable variables since those are represented differently in memory.

Rules for lateinit

Android Example


class MainActivity : AppCompatActivity() {
    lateinit var button: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        button = findViewById(R.id.submitBtn)
        button.setOnClickListener {
            Toast.makeText(this, "Clicked!", Toast.LENGTH_SHORT).show()
        }
    }
}
  

Here, the button can’t be initialized before setContentView(), so lateinit is the perfect solution.


lazy – Lazy Initialization for val

What is lazy?

The lazy delegate is used to initialize a read-only (val) property only when it’s accessed for the first time. It’s part of Kotlin’s Delegation feature.

Syntax


val variableName: Type by lazy {
    // initialization logic
}
  

Example


class User {
    val info: String by lazy {
        println("Initializing user info...")
        "User: Talha Abbas"
    }
}

fun main() {
    val user = User()
    println("Before accessing info")
    println(user.info)   // Triggers initialization
    println(user.info)   // Uses cached value
}
  

Output:


Before accessing info
Initializing user info...
User: Talha Abbas
User: Talha Abbas
  

The initialization block runs only once, the first time the property is accessed. The value is then cached for subsequent uses.

Lazy Thread Safety Modes


val data by lazy(LazyThreadSafetyMode.NONE) {
    println("Loading data...")
    "Data loaded"
}
  

Difference Between lateinit and lazy

Aspect lateinit lazy
Type Used with var (mutable) Used with val (immutable)
Initialization Time Initialized later manually Initialized automatically on first access
Thread Safety Not thread-safe Thread-safe by default
Use Case Dependency Injection, Android Views Heavy Computation, Config Loading, Caching
Null Safety Cannot be used with nullable types Works with all types

Common Interview Questions & Answers

1. What is the difference between lateinit and lazy?

Answer: lateinit is used for mutable variables (var) that will be initialized later, while lazy is used for immutable properties (val) that are initialized only when accessed for the first time.

2. Can we use lateinit with primitive types like Int or Boolean?

Answer: No. lateinit cannot be used with primitive types or nullable variables, but works with String since it’s a reference type in Kotlin.

3. Is lazy thread-safe?

Answer: Yes, by default lazy uses LazyThreadSafetyMode.SYNCHRONIZED, which ensures that initialization happens safely in multi-threaded environments.

4. When would you use lateinit in Android?

Answer: It’s commonly used for UI elements that are initialized after setContentView() is called, e.g., lateinit var button: Button.

5. What happens if you access a lateinit variable before it’s initialized?

Answer: It throws UninitializedPropertyAccessException.

6. Can lazy properties be reinitialized?

Answer: No, once initialized, a lazy property cannot be reassigned.

7. How to check if a lateinit variable is initialized?

Answer: Use the reflection syntax: if (this::variableName.isInitialized).


Conclusion

Both lateinit and lazy enhance flexibility in Kotlin’s initialization mechanism. Use lateinit when a variable’s value will be set later (like Android views), and lazy when a property should be initialized only when accessed (like configuration or caching). Understanding when and how to use each is a key concept in writing clean, efficient Kotlin code.