Dagger Fundamentals & Manual DI Interview Questions

ARCHITECTURE › Dependency Injection

Walk me through why you'd choose Dagger over just doing manual constructor injection with an AppContainer for a real production app, and where manual DI is actually still the better choice.

What a strong answer covers: Dagger buys compile-time validated dependency graphs that catch missing or duplicate bindings at build time, auto-generated wiring for a deep object graph, and scoped subcomponents for structured lifetimes, all of which matter once the graph grows past what one person can wire by hand reliably. Manual DI with an AppContainer is still the better choice for a small app, a plain library, or KMP-shared code, where the annotation-processing overhead and Android coupling aren't worth it; the trap is reaching for Dagger reflexively on a tiny app just because it's the convention elsewhere.

You're building a checkout flow made of three screens that share some in-flight state, and that state should be created when the flow starts and completely discarded when it ends. How would you model that with subcomponents rather than just using @Singleton?

What a strong answer covers: You give the flow its own scoped subcomponent with its own scope annotation, created explicitly when the flow starts and released when it completes, so the shared state's lifetime is tied exactly to the flow rather than the app. Using @Singleton for this is the common shortcut and mistake, since it leaks that state for the entire app lifetime, leaving stale checkout data around long after the user leaves and never resetting between separate checkout attempts.

Dagger throws a duplicate binding error at compile time for a type provided by two different modules installed in the same component. Walk me through how you'd actually resolve that, and what the right long-term fix is versus a quick hack.

What a strong answer covers: The quick, often-wrong hack is to delete or comment out one of the @Provides or @Binds methods until it compiles. The real fix is recognizing that two implementations legitimately need to coexist and disambiguating them with a custom @Qualifier applied at both the binding and the injection site, or, if only one should exist in that component at all, moving one module to a different subcomponent where the conflicting binding isn't installed; the trap is picking whichever binding happens to compile after deletion without checking whether both were actually needed by different consumers.

Back to Dagger Fundamentals & Manual DI