Skip to main content
Version: 4.2

Lazy Modules and Background Loading

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.

What Are Lazy Modules?

Lazy modules delay module registration and instance creation until explicitly loaded. They're particularly useful for:

  • Large applications - Split initialization across multiple threads
  • Performance optimization - Reduce startup time
  • Conditional features - Load modules only when needed
  • Background initialization - Load non-critical modules asynchronously

Defining Lazy Modules

Create lazy modules with the lazyModule function:

// Lazy module - not loaded until explicitly requested
val networkModule = lazyModule {
singleOf(::ApiClient)
singleOf(::NetworkMonitor)
}

val databaseModule = lazyModule {
singleOf(::Database)
singleOf(::UserDao)
}

Composing Lazy Modules

Lazy modules support includes() just like regular modules:

val dataModule = lazyModule {
singleOf(::UserRepository)
}

val featureModule = lazyModule {
includes(dataModule) // Include other lazy modules
singleOf(::FeatureService)
}
info

Lazy modules won't allocate any resources until loaded via the lazyModules() function.

Loading Lazy Modules

Load lazy modules using lazyModules() within your Koin configuration.

Basic Loading

val analyticsModule = lazyModule {
singleOf(::AnalyticsService)
}

val reportingModule = lazyModule {
singleOf(::CrashReporter)
}

startKoin {
// Load critical modules immediately
modules(coreModule, networkModule)

// Load non-critical modules in background
lazyModules(analyticsModule, reportingModule)
}

Parallel Loading (4.2.0+)

Since version 4.2.0, multiple lazy modules load in parallel, with each module in its own coroutine:

val module1 = lazyModule { singleOf(::DatabaseService) }
val module2 = lazyModule { singleOf(::NetworkService) }
val module3 = lazyModule { singleOf(::AnalyticsService) }

startKoin {
// All three modules load simultaneously!
lazyModules(module1, module2, module3)
}

Performance Impact:

ScenarioBefore 4.2.0 (Sequential)After 4.2.0 (Parallel)
1 module @ 100ms100ms100ms
3 modules @ 100ms each300ms~100ms
10 modules @ 100ms each1000ms~100ms

Waiting for Completion

All Platforms: 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:

  • JVM/Native: True blocking with runBlocking
  • JS: Uses GlobalScope.promise (not truly blocking, logs warning)

JVM Only: runOnKoinStarted()

startKoin {
lazyModules(analyticsModule)
}

// JVM-only callback
KoinPlatform.getKoin().runOnKoinStarted { koin ->
// Executes after all lazy modules finish loading
koin.get<AnalyticsService>().trackAppStart()
}

Suspending Alternative: 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!")
}

Custom Dispatchers

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, network
  • Dispatchers.Main - UI updates (Android/Desktop)
info

Default dispatcher is Dispatchers.Default if not specified.

Real-World Example

// Core modules - load immediately
val coreModule = module {
single { AppConfig() }
single { UserSession() }
}

// Feature modules - load in background
val analyticsModule = lazyModule {
singleOf(::AnalyticsEngine)
singleOf(::EventTracker)
}

val networkingModule = lazyModule {
singleOf(::ApiClient)
singleOf(::WebSocketManager)
}

val databaseModule = lazyModule {
singleOf(::Database)
singleOf(::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!")
}
}
}

Important Limitations

Avoid Cross-Dependencies

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)
}
danger

Koin doesn't currently validate dependencies between regular and lazy modules. Ensure regular modules don't depend on lazy module definitions.

Best Practice: Load Order

  1. Immediate modules - Critical services needed at startup
  2. Lazy modules - Non-critical, deferrable services
  3. Wait if needed - Use waitAllStartJobs() before accessing lazy definitions

When to Use Lazy Modules

Good Use Cases

  • Analytics/Tracking - Not needed for core functionality
  • Crash Reporting - Can initialize in background
  • Feature Modules - Modular features loaded on-demand
  • Database/Network - Heavy initialization that can be deferred
  • Large Apps - Split startup load across threads
  • Core Services - Critical dependencies needed immediately
  • Small Apps - Overhead may exceed benefits
  • Tightly Coupled Modules - When modules have many cross-dependencies

API Reference

FunctionPlatformDescription
lazyModules()AllLoad lazy modules in background
waitAllStartJobs()AllBlock until all lazy modules load
awaitAllStartJobs()AllSuspend until all lazy modules load
runOnKoinStarted()JVM onlyCallback after loading completes

See Also