Lazy initializaton in kotlin

Anand Nath
4 min readNov 27, 2022

--

Kotlin provides lazy keyword to be used with member variables in a class. This initializes the variable only when needed. Let’s have a deeper look into lazy in this article.

Photo by David Clode on Unsplash

Basics

Create costly objects only when required. Load from database only when required. Load the cache only when required. These statements are guiding principle for any programmer who wants to write optimized code while doing resource intensive operations. The contrary is true as a special case when there is a need to prefetch or eager load for performance reasons.

Many use cases are about initialize once and re-use. In Java, we have final member variables which support this pattern. Having final member variables are so crucial in my mind that I always look for a reason why something is not final in my code reviews. The down side is that final variables needs to be initialized in the constructor. If the final variable reference is costly, then the cost is front loaded. The other option is to load on-demand, but then the variable cannot be final and becomes mutable which is undesirable in many cases. There are complex work arounds that you can employ in Java to achieve the desired results (such as using computeIfAbsent of a ConcurrentMap or creating your own objects that abstract the final-variable-with-create-once-on-demand paradigm)

In Kotlin, the concept of lazy comes handy in such situations. Let’s look at a simple example:

Only when someone creates the class LazyCreator and then access the val mammoth, does it get created. How does lazy create these variables? You can read the lazy documentation to know more. Basically there are multiple strategies supported for lazy execution: two thread-safe ways (SYNCHRONIZED and PUBLICATION) and a non thread-safe way (NONE). I won’t go deeper into that. I will provide my recommendation on when to use what:

PUBLICATION: This strategy may result in the lazy block to be executed multiple times. But this does not block the thread. The value from first thread to execute the block is set atomically and reused by the variable. This should preferred over SYNCHRONIZED when the lazy block is not costly and idempotent.

SYNCHRONIZED: This strategy ensures that the lazy block is executed only once. But this blocks the thread. The result is cached and reused by the variable. Prefer this if the lazy block is a costly operation or is not idempotent or wastes resources if executed multiple times.

NONE: This strategy invokes the lazy block and caches the value without any locking or thread-safe strategies. Use this one with caution and only if you are sure that the variable will be accessed only by one thread.

To build a better mental model about PUBLICATION strategy, read about the Memoizer design pattern in Java Concurrency in Practice book. This pattern is the foundation of computeIfAbstent in ConcurrentMap API in Java.

Asynchronous lazy

So far the usages of lazy we have seen are simple cases of resource intensive operations. But in real world, things are not as simple. Most of the resource intensive operations such as network calls or database fetch needs to happen in a background thread. In Kotlin, we make use of flows and suspend functions for this. lazy cannot be used for invoking a suspend function. We need a different strategy here.

Let’s consider a scenario where we have a costly database operation which we would like to perform exactly once, cache the result and reuse. We can use a Deferred object to good effect.

In this example, you can see that the database operation happens exactly once and we reuse the value over and over when the public methods on AsyncLazy is invoked. When the database values are needed, a Deferred job is created and then reused.

The runner code can be modelled as follows which has multiple suspension points so that its truly async.

Output is as follows. Notice that multiple deferred objects were created, but only one was executed. The remaining were cancelled.

creating deferred 1
creating deferred 2
creating deferred 3
sleeping 1000 3
sleeping 1000 1
sleeping 1000 2
Trying to set deferred 3
deferred set correctly 3
Trying to set deferred 2
deferred not set, cancel 2
Trying to set deferred 1
deferred not set, cancel 1
deferred executing 3
Model(name=foo)
[Model(name=bar), Model(name=baz)]
[Model(name=foo), Model(name=bar), Model(name=baz)]

This approach is as effective as the Memoizer design pattern in which threads could create multiple Future objects out of which the first one successfully put into the map wins. In this example, multiple threads could end up creating Deferred objects, but the one that is atomically set to the variable wins and gets executed.

In this article we saw how lazy in Kotlin can be used, the various threading options and its limitations. We also learnt about a strategy for loading async scenarios lazily. Happy lazy coding!

--

--

Anand Nath

Works as Principal Engineer @ Microsoft Teams Mobile to solve hard problems by collaborating with some of the best minds in the industry.