lateinit vs lazy in Kotlin
Kotlin offers two ways to initialize properties lazily: lateinit and lazy. While both are used to delay initialization, they serve different use cases.
In this article, we’ll break down their differences, use cases, and real-world analogies to help you choose the right approach.
Join our upcoming 6 Weeks Android Mentorship Program
1. Understanding lateinit
What is lateinit?
The lateinit keyword is used for mutable (var) properties that will be initialized later but before first use. It is primarily used with non-nullable types (except primitive types like Int, Double, etc.).
Syntax
class User {
lateinit var name: String
fun setUserName(userName: String) {
name = userName
}
fun printName() {
if (::name.isInitialized) {
println("User name: $name")
} else {
println("Name is not initialized yet.")
}
}
}
fun main() {
val user = User()
user.setUserName("Akshay")
user.printName() // Output: User name: Akshay
}
Real-World Analogy for lateinit
Think of lateinit as a hotel room booking system. When a guest books a room, the reservation is made, but the actual check-in happens later. The room is guaranteed to exist, but the guest hasn't occupied it yet.
When to Use lateinit?
- When you must initialize a variable before first use, but not in the constructor.
- When dependency injection frameworks like Hilt or Dagger are used.
- When you need late property initialization in unit tests.
Advanced Insights on lateinit
✅ lateinit properties can be checked if they are initialized using ::property.isInitialized.
✅ lateinit cannot be used for val, as it is inherently mutable (var).
✅ Hidden Pitfall: If lateinit property is never initialized and accessed, it results in a UninitializedPropertyAccessException.
✅ Alternative: If lateinit is overused, consider using nullable types (String?) with !! assertions instead.
2. Understanding lazy
What is lazy?
The lazy keyword is used for read-only (val) properties and evaluates the assigned value only once, when accessed for the first time.
Syntax
class DatabaseConnection {
val connection: String by lazy {
println("Establishing Database Connection...")
"Connected to Database"
}
}
fun main() {
val db = DatabaseConnection()
println("Before accessing connection")
println(db.connection) // Output: Establishing Database Connection... Connected to Database
println(db.connection) // Output: Connected to Database (without re-evaluating)
}
Real-World Analogy for lazy
Think of lazy as turning on a water heater. The heater remains off until someone actually needs hot water. The moment someone turns on the hot water tap, the heater starts, but once heated, it remains available for subsequent use without restarting.
When to Use lazy?
- When a variable is expensive to compute (like database queries, API calls, or large object creations).
- When you don't need the value immediately, but only when first accessed.
- When ensuring thread safety in multi-threaded environments (lazy by default is thread-safe).
Advanced Insights on lazy
✅ The default lazy mode is thread-safe, meaning it synchronizes initialization across multiple threads.
✅ There are three lazy modes:
- LazyThreadSafetyMode.SYNCHRONIZED (default, ensures thread-safety)
- LazyThreadSafetyMode.PUBLICATION (allows multiple initializations but ensures only one is retained)
- LazyThreadSafetyMode.NONE (no synchronization, best for single-threaded scenarios)
✅ Hidden Pitfall: lazy should not be used for variables that are frequently accessed and require fast retrieval. Since it adds an internal locking mechanism, excessive usage can cause performance overhead.
3. Key Differences: lateinit vs lazy
Recommended by LinkedIn
lateinit
Usage - Works with var (mutable)
Initialization - Must be initialized before first use
Primitive Support - Not allowed
Multi-threading - Not thread-safe
Common Use Cases - Dependency injection, Android views, unit testing
lazy
Usage - Works with val (immutable)
Initialization - Initialized only on first access
Primitive Support - Allowed
Multi-threading - Thread-safe by default
Common Use Cases - Expensive object creation, Singleton patterns, thread safety
4. Which One Should You Use?
Choose lateinit if:
✅ You have a mutable property (var).
✅ You will guarantee initialization before first use.
✅ You’re using dependency injection or Android views.
Choose lazy if:
✅ You have an immutable property (val).
✅ You want to delay computation until first access.
✅ You’re dealing with expensive operations like database connections.
5. Conclusion
Both lateinit and lazy serve different purposes in Kotlin. While lateinit is useful for delaying initialization when you know it will be initialized before use, lazy ensures that a property is initialized only when required, making it ideal for expensive computations.
Understanding their differences will help you write efficient, optimized, and cleaner Kotlin code.
🔥 Senior Engineer's Take
- Overuse of lateinit can lead to runtime crashes; always check ::property.isInitialized.
- lazy properties should be used carefully in performance-critical applications.
- If lateinit and lazy both seem like options, prefer lazy as it avoids mutable state.
🚀 Do you use lateinit or lazy in your projects? Let me know your thoughts in the comments below!
Join our upcoming 6 Weeks Android Mentorship Program
Akshay Nandwana
Founder AndroidEngineers
You can connect with me on:
Time Will Tell (Since 2023 ) || Native Android Developer || Kotlin || Jetpack Compose || Data Structures And Algorithms C++ || MERN Stack
3moWorth Reading!!
Technical Consultant - Google • Helping Devs Land Jobs 🚀 • 114M+ Impression • Android Engineer & Mentor • Ex-GSoC Mentor & GDSC Lead • Ex-Zee5, Doubtnut • Kotlin, Jetpack Compose, CMP
3moYou can read here too - https://androidengineers.substack.com/p/lateinit-vs-lazy-in-kotlin