lateinit vs lazy Property in Kotlin

Since object creation is a heavy process as it initialises all the public and private properties defined in that class when the constructor is called, Kotlin has few ways to initialise properties later when required. We already discussed lateinit properties and lazy properties.

Let's try to understand some basic differences between then and when to use what. But before that let's quickly recap the lateinit and lazy properties

lateinit property

lateinit properties are the var properties that can be initialised later in the constructor or in any function according to the use.
data class User (val id : Long,
val username : String) : Serializable

lateinit var lateinitUser : User

lazy property

lazy properties are the val properties that can also be initialised later when they are called the first time.
val lazyUser : User? by lazy {
User(id = 1, username = "agrawalsuneet")

Now lets try to understand difference between them.

lateinit var whereas lazy val

lateinit can only be used with a var property whereas lazy will always be used with val property.
A lateinit property can be reinitialised again and again as per the use whereas the lazy property can only be initialised once.
lateinit var lateinitUser : User

val lazyUser : User? by lazy {
User(id = 1, username = "agrawalsuneet")

lateinit can't have custom getter or setter whereas lazy has custom getter

A lateinit property can't have a custom getter whereas a lazy property has a block that gets executed whenever the first time that property is called.
val lazyUser : User by lazy {
//can do other initialisation here
User(id = 1, username = "agrawalsuneet")


In order to check if a lateinit property is initialised or not, we can use the extension function to KProperty directly which returns a boolean if the property is initialised or not.
* Returns `true` if this lateinit property has been assigned a value, and `false` otherwise.
* Cannot be used in an inline function, to avoid binary compatibility issues.
inline val @receiver:AccessibleLateinitPropertyLiteral KProperty0<*>.isInitialized: Boolean
get() = throw NotImplementedError("Implementation is intrinsic")

Since the lazy block returns an object which implements the Lazy interface, we can check if the variable is initialised or not in the case of the lazy property also but for that, we need to split the lazy call.
val lazyUserDelegate = lazy { User(id = 1, username = "agrawalsuneet") }
val lazyUser by lazyUserDelegate


Primitive types

lateinit properties can't be of primitive data types whereas lazy properties can be of primitive date types also.
lateinit var lateinitInt : Int //compilation error: 'lateinit' modifier is not allowed on properties of primitive types

val lazyInt by lazy {

Thread Safety

We can't define the thready safety in case of lateinit property but in case of lazy, we can choose between SYNCHRONIZED, PUBLICATION and NONE.
val lazyUser : User? by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
User(id = 1, username = "agrawalsuneet")

Nullable Type

A lazy property can be of nullable type but a lateinit property can't be of nullable type.
lateinit var lateinitUser : User? //compilation error: 'lateinit' modifier is not allowed on properties of nullable types

val lazyUser : User? by lazy {
User(id = 1, username = "agrawalsuneet")

Accessing before initialisation

Accessing a lateinit property before it has been initialized throws a special exception that clearly identifies the property being accessed and the fact that it hasn’t been initialized.
We can't ever access a lazy property before it hased been initialised. Remember that the lazy property can be null but the initialisation will still happen when the first time the property will be called.

Based on the above difference we can decide when to use the lateinit property or when to use the lazy property. The developer doesn't have to remember if the property is initialised or not in case of lazy property but in case of lateinit we need to remember the flow.
There are no guidelines in order to decide when to use what but according to the use case, we can decide.