Lateinit vs Lazy in Kotlin: Key Differences and Usage
lateinit is used to declare a non-null variable that will be initialized later before use, mainly for mutable vars, while lazy is used for read-only properties that initialize once on first access. lateinit works only with var and non-primitive types, whereas lazy works with val and supports thread-safe initialization.Quick Comparison
Here is a quick side-by-side comparison of lateinit and lazy in Kotlin.
| Factor | lateinit | lazy |
|---|---|---|
| Initialization time | Must be initialized before use manually | Initialized automatically on first access |
| Variable type | Only var (mutable), non-primitive types | Only val (immutable), any type |
| Null safety | Non-null, throws exception if accessed before init | Initialized lazily, never null after initialization |
| Thread safety | No built-in thread safety | Supports thread-safe lazy initialization by default |
| Use case | When you want to set a variable later (e.g., dependency injection) | When initialization is expensive and should be delayed until needed |
| Syntax | lateinit var | val by lazy { ... } |
Key Differences
lateinit is a modifier used with var properties to tell Kotlin that the variable will be initialized later before use. It is mainly used for non-null properties that cannot be initialized in the constructor or at declaration. If you try to access a lateinit variable before it is initialized, Kotlin throws an exception called UninitializedPropertyAccessException.
On the other hand, lazy is a delegate used with val properties to delay initialization until the first time the property is accessed. This is useful when the initialization is costly or depends on some runtime information. The lazy delegate also supports thread safety by default, making it safe to use in concurrent environments.
Another important difference is that lateinit cannot be used with primitive types like Int or Boolean, while lazy can be used with any type. Also, lateinit requires manual initialization, whereas lazy handles initialization automatically.
Code Comparison
Here is how you use lateinit to declare and initialize a variable later:
class Example { lateinit var text: String fun initialize() { text = "Hello, lateinit!" } fun printText() { if (::text.isInitialized) { println(text) } else { println("text is not initialized") } } } fun main() { val example = Example() example.printText() // text is not initialized example.initialize() example.printText() // Hello, lateinit! }
Lazy Equivalent
Here is how you use lazy to initialize a value only when it is first accessed:
class Example { val text: String by lazy { println("Initializing lazy text") "Hello, lazy!" } fun printText() { println(text) } } fun main() { val example = Example() println("Before accessing text") example.printText() // Triggers initialization example.printText() // Uses cached value }
When to Use Which
Choose lateinit when: you have a mutable property that cannot be initialized at declaration but will definitely be initialized before use, such as dependency injection or Android view binding.
Choose lazy when: you want an immutable property that should be initialized only once and only when needed, especially if the initialization is expensive or you want thread safety.
In short, use lateinit for mutable vars initialized later, and lazy for read-only vals initialized on first access.
Key Takeaways
lateinit is for mutable vars initialized manually before use.lazy is for immutable vals initialized automatically on first access.lateinit cannot be used with primitives; lazy can.lazy supports thread-safe initialization by default.lateinit for late initialization, lazy for delayed, on-demand initialization.