How do you navigate between feature modules without direct dependencies?

This tests architectural decoupling in multi-module Android apps. A great answer describes a shared navigation contract or deep-link router that lets features stay independent. Recommending direct Gradle dependencies between features is a major red flag.
What's really being asked
The interviewer wants to see if you understand how to enforce strict module boundaries in a large Android codebase. They care about inversion of control, build-time independence, and whether you know how to route screens without leaking implementation details across Gradle modules. This is fundamentally about the dependency rule: feature modules should never know about each other.
The full answer
First, introduce a contract or api module such as :navigation_contract or :core_navigation that holds route constants, sealed classes, or navigator interfaces. Second, explain that the app module or a dedicated router module wires concrete destinations to these abstractions at runtime or compile time. Third, mention dynamic registration where each feature contributes its navigation graph or route handler to a central registry, often via dependency injection. Fourth, note practical Android patterns like the Navigation Component with dynamic feature modules, deep link URIs resolved by an intermediary, or compile-safe routers built with Kotlin Symbol Processing. Emphasize that the feature module only exposes its entry points, never its internals.
The mistakes people make
Saying you would let :feature_profile directly depend on :feature_settings is an immediate red flag because it creates a hard dependency graph and kills parallel build benefits. Proposing a single :common module that every feature imports is almost as bad; it becomes a dumping ground and still couples features implicitly. Another trap is suggesting reflection-based navigation without explaining how you maintain type safety or handle ProGuard and R8 obfuscation in production.
What usually comes next
The interviewer may ask how you handle arguments safely across module boundaries, how you would deep link into a dynamic feature module that is not yet installed, or how you test navigation logic in isolation. They might also probe whether your router should live in the app module or its own library, and how you prevent circular dependencies if multiple features need bidirectional routing.
A concrete example
Imagine :feature_profile needs to open :feature_settings. You define a SettingsRoute data class in :navigation_contract. The :feature_settings module provides a SettingsRouteHandler implementation registered in a Dagger or Hilt multibinding map keyed by the route class. The :app module holds a NavigationDispatcher that looks up the handler and delegates to the Android Navigation Component. :feature_profile only knows the SettingsRoute type from the contract module; it has no Gradle dependency on :feature_settings and cannot access its fragments or view models directly.
Interview question
In a large multi-module Android codebase, which pattern correctly implements inversion of control for cross-feature navigation without violating the dependency rule?
- a.Resolve destination screens via reflection on string class names to avoid explicit Gradle dependencies while keeping feature modules independent.
- b.Use a shared navigation contract module for route definitions, with each feature contributing handlers to a DI multibinding map resolved by the app module.Correct
- c.Route all inter-feature navigation through a single common module that contains every feature's entry points to prevent direct cross-dependencies.
- d.Have feature modules declare direct Gradle dependencies on one another and expose public fragment classes for external navigation.
Why? this is the answer
B correctly applies inversion of control by isolating features behind a contract module and delegating wiring to the app module via dependency injection. C is tempting but wrong because a monolithic common module still implicitly couples features and becomes an unmaintainable dumping ground.
Just read this? Test yourself on what you have been reading.
Read the original → developer.android.com
- #android
- #modularization
- #navigation
- #architecture
- #dependency-injection
You just looked this up. Could you explain it out loud?
That is the part interviews actually test. Tezvyn takes questions like this one and gives you what the interviewer is really checking, the answer that lands, and the mistake that ends the conversation, in the four minutes before your next meeting.
The iPhone app is on the way
We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.
Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.
We are hiring for this. Open roles that interview on android — each one lists the topics its interview covers.
See open roles