Dagger Fundamentals & Manual DI Quiz
ARCHITECTURE › Dependency Injection
What does Dagger generate when you annotate an interface with @Component?
- A reflection-based proxy that gets resolved lazily at runtime
- A concrete component implementation and factories for its types
- An XML descriptor that describes the dependency graph structure
- Nothing is generated until you invoke a runtime initializer
Answer: A concrete component implementation and factories for its types
Dagger generates a concrete implementation of the @Component interface at build time, along with factory classes for each type, with no reflection involved.
Which scenario is the WEAKEST case for adopting Hilt?
- A large multi-module Android app with many feature boundaries
- An app with complex, scoped dependency graphs and components
- Kotlin Multiplatform shared code or a small library module
- An app that needs strong testability through fakes and mocks
Answer: Kotlin Multiplatform shared code or a small library module
Hilt is Android-specific and ties into Android components, so it's a poor fit for KMP shared modules, standalone libraries, or tiny apps where manual constructor injection suffices.
When must you use a @Module with @Provides instead of constructor @Inject?
- When binding an interface or third-party type like Retrofit
- Whenever a class has more than two dependencies to resolve
- Only when the class must be a singleton within the graph
- Whenever the class is a ViewModel instead of a regular type
Answer: When binding an interface or third-party type like Retrofit
You can't put @Inject on the constructor of an interface or a third-party class, so a @Provides (or @Binds) method in a module tells Dagger how to supply it.
What is the primary purpose of a qualifier annotation like @Named in Dagger?
- To delay creation of a binding until it is used
- To keep a binding alive for the app's lifetime
- To enable runtime reflection on the injected type
- To distinguish bindings that share the same type
Answer: To distinguish bindings that share the same type
Qualifiers disambiguate two or more bindings that share the same Java/Kotlin type so Dagger knows which one to inject at a given site.
A key benefit of Dagger validating the graph at compile time is that:
- Missing dependencies and cycles fail the build, not at runtime
- Dependencies are resolved with runtime reflection for more flexibility
- You can swap bindings freely without needing to recompile the app
- It removes the need to declare any dependencies in the graph
Answer: Missing dependencies and cycles fail the build, not at runtime
Dagger walks the graph at build time, ensuring every dependency is satisfiable and cycle-free, so errors surface at compilation rather than as runtime exceptions.
How does Koin differ from Hilt/Dagger in how it reports a missing dependency?
- Koin reports it at compile time via annotation processing
- Both report it identically at compile time
- Koin throws at runtime when the dependency is resolved
- Koin silently injects null
Answer: Koin throws at runtime when the dependency is resolved
Koin is a runtime service locator using a Kotlin DSL, so an unregistered dependency surfaces only when it is resolved at runtime, unlike Dagger's compile-time check.
In manual DI, what best describes the AppContainer pattern?
- A Kotlin object singleton accessed through getInstance() calls
- A class on the Application that centralizes shared dependencies
- A Dagger-generated component subclass used for all app wiring
- A reflection-based locator that scans annotations at runtime
Answer: A class on the Application that centralizes shared dependencies
AppContainer is a regular class (not a singleton object) stored on the Application; it centralizes construction and sharing of app-wide dependencies, with flow containers handling shorter-lived scopes.
Why is @Binds generally preferred over @Provides when mapping an interface to a single implementation?
- @Binds methods are concrete and can include custom construction logic
- @Binds needs no method body, so Dagger generates leaner, faster code
- @Binds decides bindings at runtime, while @Provides does it at compile time
- @Binds automatically applies singleton scope to the provided binding
Answer: @Binds needs no method body, so Dagger generates leaner, faster code
@Binds is an abstract method that simply tells Dagger an impl satisfies an interface, so no method body is generated and the result is more efficient than a concrete @Provides method.
What does annotating a binding with @Singleton actually guarantee?
- The type becomes a global JVM singleton for the whole process
- Dagger uses reflection to cache and return the same instance
- One instance reused for the lifetime of its @Singleton component
- Only one instance may ever be injected, after which injection fails
Answer: One instance reused for the lifetime of its @Singleton component
A Dagger scope ties an instance's lifetime to its component, not the whole process; @Singleton on the application component means one shared instance for that component's lifetime, and a different component would hold its own.
You have a legitimate cyclic dependency where A needs B and B needs A. What is the idiomatic Dagger way to break the cycle so the graph compiles?
- Inject Provider<T> or Lazy<T> on one side to defer creation
- Mark one of the two bindings @Singleton to make it compile
- Add a qualifier annotation to one binding to break the loop
- Merge the two classes into one, since Dagger can’t resolve cycles
Answer: Inject Provider<T> or Lazy<T> on one side to defer creation
Dagger fails the build on a direct cycle, but wrapping one dependency in Provider<T> or Lazy<T> defers its creation until first use, breaking the construction-time loop.
In a @Component.Factory or @Component.Builder, what does @BindsInstance let you do?
- Bind a runtime object, like an Application or userId, into the graph
- Automatically replace a binding with a mock when running tests
- Turn a @Provides method into a @Binds method for the component
- Declare a subcomponent that stands alone without any parent component
Answer: Bind a runtime object, like an Application or userId, into the graph
@BindsInstance on a builder/factory parameter feeds an externally created object (like a Context, Application, or runtime value) into the dependency graph instead of having Dagger construct it.
Which Dagger feature lets several different modules each contribute an element into one injected Set or Map?
- Qualifier annotations that distinguish one binding from another
- Component dependencies that reuse bindings from another component
- Subcomponents that inherit and extend bindings from a parent scope
- Multibindings with @IntoSet, @ElementsIntoSet, and @IntoMap
Answer: Multibindings with @IntoSet, @ElementsIntoSet, and @IntoMap
Multibindings let modules across the codebase each add entries to a collection that Dagger aggregates into a single injectable Set<T> or Map<K, V>, a common pattern for plugin-style registries.
A class needs both Dagger-provided dependencies and a runtime parameter known only at creation time. Which Dagger feature fits best?
- Marking the class @Singleton to reuse one instance everywhere
- Declaring it with @BindsOptionalOf to make the binding optional
- Assisted injection with @AssistedInject and a factory
- Using a plain @Provides method with the runtime value hardcoded
Answer: Assisted injection with @AssistedInject and a factory
Assisted injection mixes graph-provided dependencies with caller-supplied @Assisted parameters; Dagger generates a factory you inject and call with the runtime value to build the instance.
In a modern 2026 Android build, how do Dagger and Hilt produce their factory and component code?
- By reflecting over annotations when the app starts up
- By reading an XML component graph and wiring it at runtime
- By letting R8 generate factories during release builds only
- By a compile-time annotation processor, using KSP or KAPT
Answer: By a compile-time annotation processor, using KSP or KAPT
Dagger and Hilt are codegen frameworks that run an annotation processor at compile time; modern projects use the faster KSP backend, with KAPT remaining as the legacy option, and no runtime reflection is involved.