docs/reference/koin-core/lazy-modules.md
Lazy modules enable asynchronous, parallel module loading to improve startup performance. Instead of loading all modules synchronously at startup, you can defer and parallelize module initialization.
:::info
This page uses the Koin Compiler Plugin DSL (single<T>()). See Compiler Plugin Setup for configuration.
:::
Lazy modules delay module registration and instance creation until explicitly loaded. They're particularly useful for:
Create lazy modules with the lazyModule function:
// Lazy module - not loaded until explicitly requested
val networkModule = lazyModule {
single<ApiClient>()
single<NetworkMonitor>()
}
val databaseModule = lazyModule {
single<Database>()
single<UserDao>()
}
Lazy modules support includes() just like regular modules:
val dataModule = lazyModule {
single<UserRepository>()
}
val featureModule = lazyModule {
includes(dataModule) // Include other lazy modules
single<FeatureService>()
}
:::info
Lazy modules won't allocate any resources until loaded via the lazyModules() function.
:::
Load lazy modules using lazyModules() within your Koin configuration.
val analyticsModule = lazyModule {
single<AnalyticsService>()
}
val reportingModule = lazyModule {
single<CrashReporter>()
}
startKoin {
// Load critical modules immediately
modules(coreModule, networkModule)
// Load non-critical modules in background
lazyModules(analyticsModule, reportingModule)
}
Since version 4.2.0, multiple lazy modules load in parallel, with each module in its own coroutine:
val module1 = lazyModule { single<DatabaseService>() }
val module2 = lazyModule { single<NetworkService>() }
val module3 = lazyModule { single<AnalyticsService>() }
startKoin {
// All three modules load simultaneously!
lazyModules(module1, module2, module3)
}
Performance Impact:
| Scenario | Before 4.2.0 (Sequential) | After 4.2.0 (Parallel) |
|---|---|---|
| 1 module @ 100ms | 100ms | 100ms |
| 3 modules @ 100ms each | 300ms | ~100ms |
| 10 modules @ 100ms each | 1000ms | ~100ms |
waitAllStartJobs()startKoin {
lazyModules(module1, module2, module3)
}
val koin = KoinPlatform.getKoin()
// Block until all lazy modules are loaded
koin.waitAllStartJobs()
// Now safe to use dependencies from lazy modules
val service = koin.get<AnalyticsService>()
Platform behavior:
runBlockingGlobalScope.promise (not truly blocking, logs warning)runOnKoinStarted()startKoin {
lazyModules(analyticsModule)
}
// JVM-only callback
KoinPlatform.getKoin().runOnKoinStarted { koin ->
// Executes after all lazy modules finish loading
koin.get<AnalyticsService>().trackAppStart()
}
awaitAllStartJobs()For coroutine contexts or platforms without blocking support:
suspend fun initializeApp() {
startKoin {
lazyModules(module1, module2)
}
// Await without blocking
KoinPlatform.getKoin().awaitAllStartJobs()
// Safe to proceed
println("All modules loaded!")
}
Control which dispatcher runs lazy module loading:
import kotlinx.coroutines.Dispatchers
startKoin {
// Load on IO dispatcher instead of Default
lazyModules(
databaseModule,
networkModule,
dispatcher = Dispatchers.IO
)
}
Common dispatcher choices:
Dispatchers.Default - CPU-intensive work (default)Dispatchers.IO - I/O operations, file access, networkDispatchers.Main - UI updates (Android/Desktop):::info
Default dispatcher is Dispatchers.Default if not specified.
:::
// Core modules - load immediately
val coreModule = module {
single<AppConfig>()
single<UserSession>()
}
// Feature modules - load in background
val analyticsModule = lazyModule {
single<AnalyticsEngine>()
single<EventTracker>()
}
val networkingModule = lazyModule {
single<ApiClient>()
single<WebSocketManager>()
}
val databaseModule = lazyModule {
single<Database>()
single<UserDao>()
}
// Android Application
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidLogger()
androidContext(this@MyApp)
// Critical modules load immediately
modules(coreModule)
// Non-critical modules load in parallel in background
lazyModules(
analyticsModule,
networkingModule,
databaseModule,
dispatcher = Dispatchers.IO
)
}
// Optional: Wait for background loading to complete
lifecycleScope.launch {
KoinPlatform.getKoin().awaitAllStartJobs()
Log.d("Koin", "All modules loaded!")
}
}
}
Lazy modules and regular modules should be independent. Don't create dependencies from regular modules to lazy modules:
// ❌ BAD - mainModule depends on lazy module
val lazyAnalytics = lazyModule {
single { AnalyticsService() }
}
val mainModule = module {
single { AppController(get<AnalyticsService>()) } // May fail!
}
startKoin {
modules(mainModule)
lazyModules(lazyAnalytics)
}
// ✅ GOOD - Keep dependencies separate
val lazyAnalytics = lazyModule {
single { AnalyticsService() }
}
val mainModule = module {
single { AppController() }
}
startKoin {
modules(mainModule)
lazyModules(lazyAnalytics)
}
:::warning Koin doesn't currently validate dependencies between regular and lazy modules. Ensure regular modules don't depend on lazy module definitions. :::
waitAllStartJobs() before accessing lazy definitions| Function | Platform | Description |
|---|---|---|
lazyModules() | All | Load lazy modules in background |
waitAllStartJobs() | All | Block until all lazy modules load |
awaitAllStartJobs() | All | Suspend until all lazy modules load |
runOnKoinStarted() | JVM only | Callback after loading completes |
includes()