Question
In Kotlin, if you do not want to initialize a class property in the constructor or at the top of the class body, two common options are by lazy and lateinit.
lazy() returns a delegate that initializes the value only on first access and then keeps that value for future reads:
class Hello {
val myLazyString: String by lazy { "Hello" }
}
In this case, the first access to myLazyString computes the value, and every later access returns the same stored result.
Kotlin also supports late initialization with lateinit:
class MyTest {
lateinit var subject: TestSubject
@SetUp
fun setup() {
subject = TestSubject()
}
@Test
fun test() {
subject.method()
}
}
lateinit is useful when a non-null property cannot be initialized immediately, such as in dependency injection or test setup. It can only be used on var properties declared in the class body, with non-null and non-primitive types, and without custom getters or setters.
How should you choose correctly between by lazy and lateinit in Kotlin, especially since both can appear to solve a delayed initialization problem?
Short Answer
By the end of this page, you will understand the difference between by lazy and lateinit in Kotlin, when each should be used, what rules apply to each one, and how to avoid common mistakes. You will also see practical examples from Android apps, tests, and general Kotlin code.
Concept
by lazy and lateinit both delay initialization, but they solve different kinds of problems.
by lazy means:
- the value is computed automatically the first time you use it
- the property is usually a
val, so it is read-only after initialization - Kotlin manages the initialization for you
- after the first access, the same stored value is returned
Example:
val config by lazy { loadConfigFromFile() }
This is ideal when:
- the value is expensive to create
- the value may never be needed
- the value should be created once and reused
- the object can define its own initialization logic
lateinit means:
- the value is not created automatically
- you promise Kotlin that you will assign it before using it
- the property must be a
var - if you access it too early, Kotlin throws
UninitializedPropertyAccessException
Example:
repository: UserRepository
Mental Model
Think of these two features as two different ways to get a tool you need.
by lazy
You keep the tool in a sealed box with instructions attached. The first time you need the tool, the box opens, builds the tool, and hands it to you. After that, the same tool is reused every time.
lateinit
You leave an empty space on the shelf and say, "Someone will place the tool here before I need it." Kotlin trusts you. If you reach for it before someone puts it there, you get an error.
So:
lazy= Kotlin builds it when neededlateinit= you or some external code must set it later
Syntax and Examples
Core syntax
by lazy
val name: String by lazy {
"Kotlin"
}
- Works with
val - Runs the lambda once on first access
- Stores the result
lateinit
lateinit var name: String
- Works only with
var - Must be assigned before use
- Cannot be primitive types like
Int,Boolean, orDouble
Example: use by lazy for computed setup
class DatabaseClient {
val connectionString by lazy {
println("Building connection string...")
"postgres://localhost:5432/app"
}
}
{
client = DatabaseClient()
println()
println(client.connectionString)
println(client.connectionString)
}
Step by Step Execution
Trace example with by lazy
class Example {
val message by lazy {
println("Initializing message")
"Hello"
}
}
fun main() {
val ex = Example()
println("Created object")
println(ex.message)
println(ex.message)
}
What happens step by step
val ex = Example()creates theExampleobject.messageis not initialized yet.println("Created object")prints normally.- First access to
ex.messagetriggers thelazyblock. Initializing messageis printed.- The block returns
"Hello". - Kotlin stores that value.
Hellois printed.- Second access to
ex.messagedoes run the block again.
Real World Use Cases
When by lazy is useful
Expensive objects
val parser by lazy { JsonParser() }
Create the object only if it is actually needed.
Configuration loading
val config by lazy { loadConfig() }
Useful in CLI tools, backend apps, or desktop apps where startup cost matters.
Cached values
val fullName by lazy { "$firstName $lastName" }
Good when the value should be calculated once and reused.
Android view or resource setup
Historically, lazy has often been used for values that should be created only when first used.
When lateinit is useful
Dependency injection
lateinit var apiClient: ApiClient
A framework or container may assign this after object creation.
Real Codebase Usage
In real projects, developers usually choose between these based on ownership of initialization.
Common lazy patterns
1. Deferred creation of helpers
class ReportService {
private val formatter by lazy { ReportFormatter() }
}
The class itself owns the creation logic.
2. Cached derived values
class User(private val firstName: String, private val lastName: String) {
val displayName by lazy { "$firstName $lastName" }
}
3. Read-only dependencies built from configuration
class AppContainer {
val logger by lazy { Logger("app.log") }
}
Common lateinit patterns
1. Test setup
Common Mistakes
1. Using lateinit when val would be better
Broken approach:
class Config {
lateinit var settings: String
}
If the value should be created once and never changed, lazy may better express that:
class Config {
val settings by lazy { "production" }
}
2. Accessing a lateinit property before assignment
Broken code:
class Example {
lateinit var text: String
}
fun main() {
val ex = Example()
println(ex.text)
}
This throws UninitializedPropertyAccessException.
Fix it by assigning first:
Comparisons
by lazy vs lateinit
| Feature | by lazy | lateinit |
|---|---|---|
| Initialization happens | Automatically on first access | Manually before first use |
| Typical property type | val | var |
| Nullable required? | No | No |
| Can fail if accessed too early? | No, it initializes itself | Yes |
| Reassignable? | No, usually read-only | Yes |
| Primitive types allowed? | Yes, depending on the property type | No |
Cheat Sheet
Quick rules
- Use
by lazywhen a value should be created automatically on first use. - Use
lateinitwhen a value will be assigned later by other code. - Prefer constructor parameters when the dependency is always required.
by lazy
val value by lazy { createValue() }
- Usually used with
val - Runs once on first access
- Caches the result
- Good for expensive setup or optional values
lateinit
lateinit var value: MyType
- Only for
var - Only for non-null, non-primitive types
- Must be assigned before access
- Good for test setup and dependency injection
Check if lateinit is initialized
if (::value.isInitialized) {
println(value)
}
Choose this way
FAQ
When should I use by lazy instead of lateinit in Kotlin?
Use by lazy when the property can create its own value on first access and should keep that value afterward.
When should I use lateinit in Kotlin?
Use lateinit when the property must be assigned later, usually by setup code, dependency injection, or a framework lifecycle.
Is lateinit better than nullable properties?
Not always. Use lateinit when the property should definitely be initialized before use. Use nullable types when missing data is a real valid state.
Can lateinit be used with val?
No. lateinit only works with var because the value is assigned after object creation.
Can by lazy be used with var?
The common pattern is val by lazy. It represents one-time initialization and cached read-only access.
What happens if I access a property before setting it?
Mini Project
Description
Build a small Kotlin example that shows both delayed initialization styles in one place. The program will simulate an app service that creates a logger only when needed and receives a repository later during setup. This demonstrates the real difference between self-managed initialization and externally assigned initialization.
Goal
Create a class that uses by lazy for a read-only helper object and lateinit for a dependency assigned after object creation.
Requirements
- Create a
Loggerclass and aUserRepositoryclass. - In a service class, use
by lazyfor the logger. - In the same service class, use
lateinitfor the repository. - Assign the repository after creating the service object.
- Call a method that uses both properties correctly.
Keep learning
Related questions
Android AlarmManager Example: Scheduling Tasks with AlarmManager
Learn how to use Android AlarmManager to schedule tasks, set alarms, and handle broadcasts with a simple beginner example.
Can You Extend a Data Class in Kotlin? Inheritance, Limits, and Better Alternatives
Learn why Kotlin data classes cannot be extended, what causes the component function clash, and which alternatives to use instead.
Difference Between List and Array in Kotlin
Learn the difference between List and Array in Kotlin, including mutability, size, APIs, performance, and when to use each one.