Android Dependency Injection: Hilt vs Koin - Complete Guide and Interview Questions 2026

Compare Hilt and Koin for Android dependency injection with practical code examples, performance benchmarks, and common interview questions. Updated for Hilt 2.57 and Koin 4.2.

Android dependency injection comparison between Hilt and Koin frameworks

Android dependency injection determines how classes receive their dependencies instead of creating them directly. Two frameworks dominate the ecosystem in 2026: Hilt (Google's compile-time solution built on Dagger) at version 2.57.1, and Koin (a lightweight runtime service locator) at version 4.2.1. Choosing the right one affects build times, startup performance, testability, and how quickly a team can onboard new developers.

Compile-Time vs Runtime DI

Hilt validates the entire dependency graph during compilation and fails the build if any binding is missing. Koin resolves dependencies at runtime using a Kotlin DSL, catching configuration errors only when the affected code path executes. This fundamental difference drives every trade-off between the two frameworks.

How Hilt Dependency Injection Works Under the Hood

Hilt generates Dagger components mapped to Android lifecycle classes. The @HiltAndroidApp annotation triggers code generation at compile time, producing factories and providers for every declared binding. KSP (Kotlin Symbol Processing) replaced KAPT as the recommended annotation processor starting with Hilt 2.48, cutting annotation processing time roughly in half.

A typical Hilt setup involves three pieces: a module declaring bindings, an entry point (Activity, Fragment, or ViewModel), and the Application class annotated with @HiltAndroidApp.

AppModule.ktkotlin
@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Provides
    @Singleton
    fun provideRetrofit(): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    @Provides
    @Singleton
    fun provideUserRepository(
        retrofit: Retrofit
    ): UserRepository {
        return UserRepositoryImpl(retrofit.create(UserApi::class.java))
    }
}
UserViewModel.ktkotlin
@HiltViewModel
class UserViewModel @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {

    private val _users = MutableStateFlow<List<User>>(emptyList())
    val users: StateFlow<List<User>> = _users.asStateFlow()

    fun loadUsers() {
        viewModelScope.launch {
            _users.value = userRepository.getUsers()
        }
    }
}

The compiler verifies that UserRepository has a valid provider before the app ever runs. Missing bindings surface as build errors, not runtime crashes.

Koin Setup and the Kotlin DSL Approach

Koin takes the opposite approach: zero code generation, zero annotation processing. Dependencies are declared in plain Kotlin using a DSL, and the framework resolves them at runtime through a global service registry. Koin 4.2 introduced lazy modules for parallel loading at startup and a new CoreResolverV2 engine that optimizes scope resolution.

AppModule.ktkotlin
val appModule = module {
    // Singleton Retrofit instance
    single<Retrofit> {
        Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    // Singleton repository bound to its interface
    single<UserRepository> {
        UserRepositoryImpl(get<Retrofit>().create(UserApi::class.java))
    }

    // ViewModel with injected repository
    viewModel { UserViewModel(get()) }
}
MyApplication.ktkotlin
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidContext(this@MyApplication)
            modules(appModule)
        }
    }
}

The DSL reads naturally and requires no annotation processing plugin in the Gradle config. The trade-off: a typo in get() resolution only surfaces at runtime when that specific injection point executes.

Performance Benchmarks: Build Time vs Runtime Cost

The performance question comes up in almost every Android interview. Concrete numbers matter more than opinions.

| Metric | Hilt 2.57 (KSP) | Koin 4.2 | |--------|-----------------|----------| | Clean build overhead | +8-15s (KSP code generation) | ~0s (no code generation) | | Incremental build overhead | +2-4s | ~0s | | App startup (50 bindings) | ~0ms (pre-generated code) | ~5-15ms (graph resolution) | | App startup (500 bindings) | ~0ms | ~30-80ms | | APK size impact | +200-400 KB (generated code) | +100 KB (runtime library) | | Missing binding detection | Compile error | Runtime crash |

For projects with fewer than 100 bindings, the runtime overhead of Koin stays imperceptible. Beyond 300 bindings, the startup cost becomes measurable, though Koin 4.2's lazy modules mitigate this by parallelizing module loading.

KSP vs KAPT

Switching from KAPT to KSP for Hilt processing reduces annotation processing time by 40-60%. Any project still using KAPT with Hilt should migrate to KSP immediately. Hilt has supported KSP since version 2.48.

Testing Strategies with Each Framework

Testability is where the architectural differences become practical. Hilt provides @TestInstallIn to swap production modules with test doubles at compile time. Koin offers loadKoinModules() to override definitions at runtime.

kotlin
// Hilt test module — replaces AppModule bindings in tests
@Module
@TestInstallIn(
    components = [SingletonComponent::class],
    replaces = [AppModule::class]
)
object FakeAppModule {

    @Provides
    @Singleton
    fun provideUserRepository(): UserRepository {
        return FakeUserRepository() // In-memory test double
    }
}
kotlin
// Koin test — override at runtime
class UserViewModelTest : KoinTest {

    @Before
    fun setUp() {
        startKoin {
            modules(
                module {
                    single<UserRepository> { FakeUserRepository() }
                    viewModel { UserViewModel(get()) }
                }
            )
        }
    }

    @Test
    fun `loads users from repository`() {
        val viewModel: UserViewModel = get()
        viewModel.loadUsers()
        assertEquals(3, viewModel.users.value.size)
    }

    @After
    fun tearDown() {
        stopKoin()
    }
}

Hilt tests catch misconfigured test modules at compile time. Koin tests require careful lifecycle management (startKoin/stopKoin) but offer more flexibility for partial module overrides.

Ready to ace your Android interviews?

Practice with our interactive simulators, flashcards, and technical tests.

Multi-Module Architecture: Scaling DI Across Features

Large Android projects split features into Gradle modules. The DI framework must support module boundaries cleanly.

With Hilt, each feature module declares its own @Module annotated with @InstallIn. Hilt merges all modules into a single component hierarchy at compile time. The MVVM architecture pairs naturally with Hilt's scoped components.

feature-payments/PaymentModule.ktkotlin
@Module
@InstallIn(ViewModelComponent::class)
object PaymentModule {

    @Provides
    fun providePaymentGateway(
        retrofit: Retrofit // Provided by :app module
    ): PaymentGateway {
        return StripePaymentGateway(retrofit.create(PaymentApi::class.java))
    }
}

With Koin, feature modules export their module {} declarations, and the app module loads them all at startup. Koin 4.2's lazy modules allow deferred loading of feature modules until first access.

feature-payments/paymentModule.ktkotlin
val paymentModule = module {
    factory<PaymentGateway> {
        StripePaymentGateway(get<Retrofit>().create(PaymentApi::class.java))
    }
}

// app/MyApplication.kt — loads all feature modules
startKoin {
    modules(appModule, paymentModule, analyticsModule)
}

Hilt enforces dependency visibility through component scopes. Koin relies on convention — any module can access any other module's definitions, which provides flexibility but can lead to implicit coupling.

Kotlin Multiplatform Considerations

Koin supports Kotlin Multiplatform (KMP) natively. The same module {} DSL works on Android, iOS, Desktop, and Web targets. Hilt is Android-only — it depends on Android-specific lifecycle components and the Dagger annotation processor.

For projects targeting multiple platforms, Koin is the only viable option between the two. Shared business logic modules can declare their dependencies once and inject them across all targets.

Common Interview Questions on Android DI

These questions come up regularly in Android technical interviews. Each answer stays concise and focuses on what interviewers expect.

Q: What is the difference between compile-time and runtime dependency injection?

Compile-time DI (Hilt/Dagger) generates injection code during the build. The compiler validates the entire dependency graph, catching missing bindings before runtime. Runtime DI (Koin) resolves dependencies when they are first requested, using a service registry pattern. Compile-time DI produces faster startup but slower builds; runtime DI has zero build overhead but defers error detection.

Q: What Hilt component scopes exist and how do they map to Android lifecycles?

SingletonComponent lives for the entire application. ActivityRetainedComponent survives configuration changes. ViewModelComponent is scoped to a ViewModel's lifecycle. ActivityComponent, FragmentComponent, and ViewComponent follow their respective Android lifecycle owners. Custom scopes can extend this hierarchy.

Q: How does Koin handle ViewModel injection in Jetpack Compose?

Koin provides koinViewModel() as a Composable function that creates or retrieves a ViewModel scoped to the nearest ViewModelStoreOwner. Since Koin 4.2, koinNavViewModel() scopes ViewModels to navigation graph entries when using Jetpack Compose navigation.

Q: When would Koin be a better choice than Hilt?

Koin fits better for KMP projects (Hilt is Android-only), small-to-medium apps where build time matters more than startup time, and teams that prefer explicit Kotlin DSL over annotation-driven configuration. Prototyping and proof-of-concept projects also benefit from Koin's minimal setup.

Common Interview Mistake

Calling Koin a "dependency injection framework" is technically imprecise. Koin is a service locator — dependencies are pulled via get() rather than pushed via constructor injection. Interviewers familiar with the distinction will expect candidates to acknowledge this. Hilt/Dagger performs true dependency injection through generated constructor calls.

Decision Framework: Choosing Between Hilt and Koin

| Factor | Hilt | Koin | |--------|------|------| | Graph validation | Compile-time | Runtime | | Build time impact | Higher (KSP codegen) | None | | Startup cost | Near-zero | Scales with graph size | | Learning curve | Steeper (Dagger concepts) | Gentle (pure Kotlin DSL) | | KMP support | No | Yes | | Jetpack integration | Deep (official Google) | Good (community) | | Team scalability | Stronger (enforced scopes) | Flexible (convention-based) | | Testing | @TestInstallIn (compile-safe) | loadKoinModules (runtime) |

Neither framework is universally superior. The choice depends on project size, team experience, and platform targets.

Start practicing!

Test your knowledge with our interview simulators and technical tests.

Conclusion

  • Hilt 2.57 with KSP delivers compile-time graph validation and near-zero startup cost, making it the stronger choice for large, single-platform Android projects with strict reliability requirements
  • Koin 4.2 with lazy modules and CoreResolverV2 closes the startup performance gap while maintaining zero build overhead and full KMP compatibility
  • Both frameworks handle multi-module architectures, but Hilt enforces scope boundaries at compile time while Koin relies on team conventions
  • For Android interview preparation, understanding the compile-time vs runtime trade-off and being able to articulate when each framework fits best is the expected baseline
  • Migrating between frameworks is non-trivial — the initial choice should account for the project's long-term platform and scaling trajectory

Start practicing!

Test your knowledge with our interview simulators and technical tests.

Tags

#android
#kotlin
#hilt
#koin
#dependency-injection
#dagger

Share

Related articles