by lazy or lateinit: which is faster in Kotlin?

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: The Eternal Dilemma of a Kotlin Developer



If you've ever written in Kotlin and faced the need to delay property initialization, you've probably asked yourself: "What should I choose — by lazy or lateinit?" This question is a real hit on Stack Overflow (thousands of views, dozens of discussions). And for good reason: the right choice affects not only code readability but also the performance of your application.



In this article, we won't just paraphrase the documentation. We'll conduct a real investigation: break down the internal workings of both mechanisms, compare their speed, memory consumption, and give clear recommendations. And at the end — we'll summarize with specific action items. Let's go!



What are lateinit and by lazy? A Quick Primer



First, let's refresh the basic concepts. Both approaches solve the same problem: deferring property initialization until its first use. But they do it differently.



lateinit var — A Promise to the Compiler



The lateinit keyword tells the compiler: "I promise to initialize this property before the first access, for now just allocate space for it." It is used only with var and only for mutable properties. It's ideal for dependency injection (Dagger, Hilt, Koin) or for properties initialized in onCreate of an Activity/Fragment.



class MyActivity : AppCompatActivity() {    lateinit var repository: UserRepository

override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) repository = UserRepository() // initialization }

fun loadUser() { // If repository is not initialized — will throw UninitializedPropertyAccessException repository.getUser() }}


by lazy — A Lazy Delegate



The lazy delegate is a more flexible mechanism. It creates a wrapper object that, upon first access, calls a lambda and caches the result. All subsequent accesses simply return the stored value. By default, it is thread-safe (synchronized), but the mode can be configured.



class MyViewModel : ViewModel() {    val heavyData: ExpensiveData by lazy {        // This block will execute only once, upon first access        ExpensiveData()    }}


Both approaches solve the "lazy initialization" problem, but they do so with different trade-offs. Let's examine these trade-offs under a microscope.



Performance: Which is Faster?



Here we get to the most interesting part — the numbers. Performance is not just about access speed to a property, but also the overhead of initialization, memory, and thread safety.



First Access Speed



lateinit is the absolute champion. On first access, the compiler simply checks that the field is not null (if it is nullable — throws an exception). No lambdas, no synchronization. It's practically free.



by lazy is slower. On first access, a synchronized block is called (if using the default mode LazyThreadSafetyMode.SYNCHRONIZED), the lambda executes, and the result is cached. This takes microseconds, but in high-load loops or critical code, it can be noticeable.



Subsequent Access Speed



After initialization, both approaches work equally fast. lateinit simply reads the field (like a regular var). by lazy after the first initialization also reads the cached value. The speed difference at this stage approaches zero.



Memory Consumption



Here, lateinit wins hands down. It does not

Blogs

Book Recommendations