Kotlin Lazy vs Lateinit Properties. When to use which property?

Ankit Sinhal
4 min readDec 26, 2021

--

Kotlin provides many great features. We can leverage these features and build a high-quality application quickly. Among all those features, lateinit and lazy are important initialization properties. It is necessary to know when to use lateinit and when to use lazy initialization.

‘lateinit’

There are times when a variable’s value is not available at the site of its declaration. An obvious example of this for Android developers is a UI widget used in an Activity or Fragment. It is not until the onCreate or onCreateView method runs that the variable, used throughout the activity to refer to the widget, can be initialized. The submitButton in this example, for instance:

Example 1.1 object initialization without lateint

class HomeFragment: Fragment() {
// we will provide actual value later
private var submitButton: Button? = null
}

The variable must be initialized. A standard technique, since we can’t know the value yet, is to make the variable nullable and initialize it with null.

However, the problem with using a nullable type is that whenever you use submitButton in your code, you will have to check for nullability. For example: submitButton?.setOnClickListener { .. }. A couple of variables like this and you’ll end up with a lot of annoying question marks! This can look particularly cluttered if you are used to Java and its simple dot notation.

Why, you might ask, does Kotlin prevent me from declaring the submitButton using a non-null type when you are sure that you will initialize it before anything tries to access it?

It’s possible. You can do exactly that using the lateinit modifier, as shown in the below code:

Example 1.2 object initialization with lateint

class HomeFragment: Fragment() {
private lateinit var submitButton: Button // will initialize later
}

Because the variable is declared lateinit, Kotlin will let you declare it without assigning it a value. The variable must be mutable, a var, because, by definition, you will assign a value to it, later. Great—problem solved, right?

When you use lateinit, you’re telling the compiler, “I don’t have a value to give you right now. But I’ll give you a value later, I promise.” If something goes wrong, it’s on you.”

By using the lateinit modifier, you disable Kotlin’s null safety for your variable. If you forget to initialize the variable or try to call some method on it before it’s initialized, you’ll get UninitializedPropertyAccessException, which is essentially the same as getting NullPointerException in Java.

When to use lateinit initialization

  • If a variable is mutable and can be initialized at a later stage.
  • You are sure about initializing a variable before using it.
  • using var keyword.

Lazy Properties

It’s a common pattern in software engineering to put off creating and initializing an object until it is actually needed. This pattern is known as lazy initialization, and is especially common on Android, since allocating a lot of objects during app startup can lead to a longer startup time. Below is a typical case of lazy initialization in Java.

Example 2.1 Java lazy initialization

class MyClass {
private HeavyObject heavy;
public HeavyObject getHeavy() {
if (heavy == null) {
heavy = new HeavyObject();
}
return heavy;
}
}

The field heavy is initialized with a new instance of the class HeavyObject (which is, presumably, expensive to create) only when its value is first requested with a call, for example, to myClass.getHeavy(). Subsequent calls to getHeavy() will return the cached instance.

In Kotlin, lazy initialization is a part of the language. By using the directive by lazy and providing an initialization block, the rest of the lazy instantiation is implicit, as shown below:

Example 2.2 Kotlin lazy initialization

class MyClass {
val heavy by lazy { // Initialization block
HeavyObject()
}
}

Notice that the code in Example 2.1 isn’t thread-safe. Multiple threads calling MyClass’s getHeavy() method simultaneously might end up with different instances of Heavyweight.

By default, the code in Example 2.2 is thread-safe. Calls to MyClass::getHeavy() will be synchronized so that only one thread at a time is in the initialization block.

Fine-grained control of concurrent access to a lazy initialization block can be managed using LazyThreadSafetyMode.

A Kotlin lazy value will not be initialized until a call is made at runtime. The first time the property heavy is referenced, the initialization block will be run.

When to use Lazy initialization

  • Variable will not be initialized unless you call it.
  • Initializes the variable once; that same value is then used throughout the code.
  • Used in case of val property i.e. read-only properties as the same object will be shared throughout the program.

That's all folks. Stay tuned for upcoming articles. For any quires or suggestions, feel free to hit me on LinkedIn. Thank you !!!

Keep Exploring, Keep Learning, Keep Growing

--

--

Ankit Sinhal

Senior Software Engineer at Walmart. Dreamer and Achiever..