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 classes
Our Courses
lateinit
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.).
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
}
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.
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.
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.
lazy
lazy
?The lazy
keyword is used for read-only (val
) properties and evaluates the assigned value only once, when accessed for the first time.
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)
}
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.
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).
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.
lateinit
vs lazy
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
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
lateinit
if:✅ You have a mutable property (var
).
✅ You will guarantee initialization before first use.
✅ You’re using dependency injection or Android views.
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.
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.
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.
You can connect with me on:
Join our upcoming classes
Our Courses