Top 40 Android Developer Interview Questions (2026)
Interview Questions · Android · 2026
Top 40 Android Developer Interview Questions (2026)
March 2026
25 min read
For hiring managers & CTOs
40 questions that reveal real Android expertise — covering Kotlin depth, Jetpack Compose, architecture patterns, Coroutines, testing, and Android 14 features. Each question includes what a strong answer looks like and why the question matters.
40Questions covering the full Android stack — from Kotlin fundamentals to production engineering
3Difficulty levels: Easy / Medium / Advanced — use them to structure your interview flow
2026Updated for Android 14, Compose Multiplatform, and Kotlin 2.0
Section 1: Kotlin Language Depth (Q1–Q8)
EasyQ1. What is the difference between val and var in Kotlin, and how does this relate to thread safety?
Strong answer:val declares a read-only reference (the reference cannot be reassigned), while var is mutable. However, val does not guarantee immutability of the object it points to — a val list can still have items added to it. For thread safety, even val references to mutable collections require synchronisation. Truly immutable data requires using immutable collection types like listOf() or value classes.
Why it matters: Developers who only say “val is like final” miss the mutability distinction — a common source of subtle concurrency bugs in Android.
EasyQ2. Explain Kotlin data classes. When would you NOT use a data class?
Strong answer: Data classes auto-generate equals(), hashCode(), toString(), copy(), and componentN() functions. Don’t use data classes when: (1) the class needs inheritance (data classes cannot be abstract base classes in most patterns), (2) equality should be reference-based rather than structural (like View objects), (3) the class has many optional properties better served by a Builder pattern.
Why it matters: Most junior devs only know the “use for model classes” rule. Senior devs know the exceptions and why they matter.
MediumQ3. What are Kotlin extension functions and what are their limitations?
Strong answer: Extension functions add new functions to existing classes without modifying them or using inheritance. They’re resolved statically at compile time (not virtually), meaning they cannot override member functions and don’t have access to private members. Limitations: cannot be overridden polymorphically, can cause confusion if the same extension is defined in multiple places, and they don’t actually modify the class.
Why it matters: Knowing the static resolution limitation prevents surprises when working with class hierarchies.
MediumQ4. Explain Kotlin sealed classes vs enums vs sealed interfaces. When do you use each?
Strong answer: Enums: fixed set of singleton instances with no state differences between instances. Sealed classes: fixed hierarchy where each subclass can hold different data (like a Result type with Success holding data and Error holding a Throwable). Sealed interfaces (Kotlin 1.5+): like sealed classes but allow multiple inheritance. Use sealed classes/interfaces for representing states, results, and events in MVI/Redux patterns — they work beautifully with when expressions for exhaustive checking.
Why it matters: Sealed classes are central to modern Android architecture. Candidates who still use boolean flags for state management haven’t adopted current patterns.
MediumQ5. What is the difference between apply, let, run, with, and also in Kotlin?
Strong answer: All are scope functions but differ in context object access and return value. apply: returns the object itself, uses this — for object initialisation. also: returns the object, uses it — for side effects. let: returns lambda result, uses it — for null checks and transformations. run: returns lambda result, uses this — for computing a result on an object. with: non-extension, takes object as parameter — for calling multiple methods on an object.
AdvancedQ6. Explain Kotlin’s inline functions and reified type parameters. What problem do they solve?
Strong answer: Inline functions copy the function body at the call site at compile time, eliminating lambda object allocation overhead. Reified type parameters solve JVM type erasure — normally, generic type T is erased at runtime. With inline + reified T, the actual type is substituted at the call site, allowing T::class and is T checks at runtime. Classic use case: inline fun <reified T> Intent.getParcelable(key: String): T?.
Why it matters: Understanding reified types is a clear indicator of senior Kotlin knowledge.
AdvancedQ7. What are Kotlin delegates (by keyword) and where do you use them in Android?
Strong answer: Kotlin delegation allows an object to delegate implementation of an interface to another object, or a property to delegate its get/set to another object. Android use cases: by viewModels() for ViewModel lazy initialisation, by lazy {} for expensive computations, by Delegates.observable() for watching property changes, by navArgs() for navigation arguments. Class delegation via by allows composing behaviour without inheritance.
AdvancedQ8. Explain Kotlin Flow vs LiveData. When would you still choose LiveData?
Strong answer: Flow is a cold, asynchronous stream from Kotlin Coroutines — is more powerful, composable, and not Android-specific. LiveData is lifecycle-aware, automatically stops observing when the View is inactive, and is simpler. Choose Flow for: complex stream transformations, sharing state between multiple collectors (StateFlow/SharedFlow), non-Android modules. Still use LiveData for: very simple MVVM where the lifecycle-awareness is the only concern, teams not yet comfortable with coroutines, or when the ViewModel exposes state consumed only by a single Observer.
Section 2: Jetpack Compose & UI (Q9–Q16)
EasyQ9. What is the difference between remember and rememberSaveable in Compose?
Strong answer:remember stores a value across recompositions but loses it on configuration changes (rotation) or process death. rememberSaveable uses the saved instance state mechanism to persist through configuration changes. Use rememberSaveable for UI state that should survive rotation (scroll position, text input values, expanded/collapsed states). Use remember for values that can be cheaply recomputed (derived state, computed values).
EasyQ10. What triggers recomposition in Jetpack Compose?
Strong answer: Recomposition is triggered when a State object that was read during a composition changes. Compose uses snapshot state (via mutableStateOf, mutableStateListOf, etc.) and tracks which composable functions read which state objects. When state changes, only the composable functions that read that specific state are recomposed — not the entire tree. Smart recomposition skips functions whose inputs haven’t changed (composable functions are effectively idempotent).
MediumQ11. What is the Compose recomposition scope and how do you minimise unnecessary recompositions?
Strong answer: The recomposition scope is the smallest composable that reads a given state. Strategies to minimise unnecessary recompositions: (1) Use derivedStateOf for computations based on other state — avoids recomposing for intermediate values. (2) Pass only the data a composable needs, not the entire object. (3) Use key() in lazy lists to help Compose identify items. (4) Prefer stable types — unstable types (like plain data classes with mutable fields) force recomposition. (5) Use @Stable and @Immutable annotations for classes you know are stable.
MediumQ12. How do you handle side effects in Jetpack Compose?
Strong answer: Compose provides several side-effect APIs: LaunchedEffect(key) — launches a coroutine tied to the composition, re-launched when key changes (for one-time events, data loading). DisposableEffect(key) — for effects that need cleanup (registering/unregistering listeners). SideEffect — for synchronising Compose state with non-Compose code after every successful recomposition. rememberCoroutineScope() — for launching coroutines from event handlers. The key principle: effects should be controlled and tied to the composition lifecycle.
MediumQ13. Explain state hoisting in Compose. Why is it important?
Strong answer: State hoisting moves state up the composition tree so that a composable becomes stateless — its state is passed in from the caller, and events are emitted back via callbacks. This makes composables more reusable (they don’t own their state), more testable (inject any state for testing), and enables the single source of truth principle. The pattern: value: T parameter going down, onValueChange: (T) -> Unit callback going up.
AdvancedQ14. How do you migrate an existing XML-based screen to Jetpack Compose incrementally?
Strong answer: Incremental migration uses interoperability APIs: (1) ComposeView to embed Compose inside an existing XML layout. (2) AndroidView composable to embed an XML View inside Compose. (3) Start with leaf UI components that have no dependencies on Android Views, migrate them to composables. (4) Share ViewModels — Compose can observe LiveData/StateFlow from existing ViewModels using observeAsState()/collectAsStateWithLifecycle(). Migration typically goes: small components → screens → navigation.
AdvancedQ15. What is Compose’s rendering pipeline and how does it differ from the View system?
Strong answer: The View system uses a stateful object tree — each View is a stateful object that mutates itself. Compose’s pipeline has three phases: Composition (executing composable functions to build a UI tree), Layout (measuring and placing each node), Drawing (rendering to the canvas). Compose uses a gap buffer (slot table) to efficiently track composition. Key difference: the View system mutates existing objects; Compose regenerates the UI tree description and diffs it. This enables smart recomposition but requires developers to think differently about state.
AdvancedQ16. How do you implement custom layouts in Compose?
Strong answer: Use the Layout composable, which takes a content lambda and a MeasurePolicy. In the measure block, you receive measurables, call measurable.measure(constraints) to get placeables, calculate the layout size, then call layout(width, height) {} and use placeable.placeRelative(x, y) to position children. For custom drawing, use Canvas composable or Modifier.drawBehind/drawWithContent. This is how Compose builds Row, Column, and Box internally.
Section 3: Architecture & Patterns (Q17–Q24)
EasyQ17. Explain the MVVM architecture pattern and the role of ViewModel in Android.
Strong answer: MVVM separates: Model (data/domain layer), View (Activities/Fragments/Composables), ViewModel (bridges View and Model, survives configuration changes). The ViewModel exposes UI state via StateFlow/LiveData and handles user events. The View observes state and emits events. Key benefit: ViewModel survives rotation (it lives longer than Views), so UI state isn’t lost. Google’s recommended architecture in 2026 adds a Repository layer between ViewModel and data sources for testability.
MediumQ18. What is MVI (Model-View-Intent) architecture and how does it differ from MVVM?
Strong answer: MVI adds strict unidirectional data flow: the View emits Intents (user actions), the ViewModel/Reducer processes them and produces a new immutable State, the View renders the State. Differences from MVVM: (1) Single state object (no separate LiveData fields), (2) Strictly unidirectional — no two-way data binding, (3) State is immutable (new state replaces old), (4) Side effects are explicitly managed as separate Events. MVI is more predictable and testable but has more boilerplate. Orbit-MVI and Uniflow are popular Kotlin libraries for it.
MediumQ19. What is the Repository pattern and why is it important for testability?
Strong answer: The Repository acts as a single source of truth for data, abstracting the data source (API, database, cache) behind an interface. The ViewModel depends on the Repository interface, not the concrete implementation. This enables: (1) Offline-first apps via local database as source of truth, (2) Testing — inject a fake Repository implementation that returns controlled test data, (3) Caching logic centralised in one place. Without Repository, the ViewModel knows about network and database details, making it impossible to unit test without real network calls.
MediumQ20. Explain Dependency Injection in Android. Hilt vs Koin — when would you choose each?
Strong answer: DI provides dependencies to objects rather than having objects create them, enabling testability and decoupling. Hilt: Google-recommended, built on Dagger, annotation-based, compile-time verification, better performance, more boilerplate setup. Koin: Kotlin DSL-based, runtime resolution, easier setup, slightly slower (service locator pattern at runtime). Choose Hilt for: larger projects, performance-sensitive apps, teams familiar with Dagger, when compile-time safety matters. Choose Koin for: smaller projects, faster onboarding, Kotlin Multiplatform compatibility.
MediumQ21. How does the Android Navigation Component handle the back stack?
Strong answer: The Navigation Component maintains a back stack of destinations. Each navigation action pushes a destination; the back button pops it. The NavController manages the stack. Key features: popUpTo clears entries up to a destination when navigating (e.g., clear login from back stack after auth). launchSingleTop avoids duplicate destinations. Safe Args generates type-safe argument passing. In Compose, NavHost with composable destinations replaces Fragment transactions. Deep links can place specific destinations in the back stack.
AdvancedQ22. Explain Clean Architecture in Android. What are the layers and the dependency rule?
Strong answer: Clean Architecture has concentric layers: Presentation (ViewModel, UI), Domain (Use Cases, Entities, Repository interfaces), Data (Repository implementations, data sources, DTOs). The dependency rule: outer layers depend on inner layers — never the reverse. The Domain layer has zero Android dependencies (pure Kotlin), making it unit-testable without Android framework. Use Cases encapsulate single business operations. Data models are mapped to domain Entities at the repository boundary. This separation is overkill for small apps but essential for large teams working on the same codebase.
AdvancedQ23. How do you implement offline-first architecture in Android?
Strong answer: The pattern: (1) Repository fetches from remote, writes to local database (Room). (2) UI observes the local database as the single source of truth. (3) Network state determines whether to show fresh data or a cached state indicator. Implementation: Room Flow emissions drive UI updates. WorkManager handles background sync when network is restored. Conflict resolution strategy must be defined (last-write-wins, server-authoritative, etc.). Room with @Transaction ensures atomic updates. The UI never reads directly from network — always from database.
AdvancedQ24. What is Paging 3 and how does it work with Compose?
Strong answer: Paging 3 is Jetpack’s library for loading large datasets in pages. Key components: PagingSource defines how to load data (network/database), Pager configures page size and prefetch distance, PagingData is the result stream. With Compose: collectAsLazyPagingItems() converts the Flow into a LazyPagingItems object usable in LazyColumn. RemoteMediator handles the network+database pattern — fetches from network, saves to Room, Room serves as the paging source for offline support.
Section 4: Coroutines & Async (Q25–Q30)
EasyQ25. What is structured concurrency in Kotlin Coroutines?
Strong answer: Structured concurrency means coroutines have a defined lifetime through their CoroutineScope. A parent coroutine cannot complete until all its children complete. If a parent is cancelled, all its children are cancelled. If a child fails, the parent (and siblings) are cancelled by default. In Android, viewModelScope cancels all coroutines when the ViewModel is cleared; lifecycleScope when the lifecycle owner is destroyed. This prevents leaked coroutines that continue running after their context is gone.
MediumQ26. Explain the difference between launch, async, and runBlocking.
Strong answer:launch: fire-and-forget, returns a Job, result is not awaited. async: returns a Deferred, call .await() to get the result — used for parallel execution where you need the result. runBlocking: blocks the current thread until coroutine completes — ONLY for main functions and testing, never in production Android code (blocks the main thread, causing ANR). Use async/await when two operations can run in parallel and you need both results before proceeding.
MediumQ27. What are Dispatchers in Kotlin Coroutines and when do you use each?
Strong answer: Dispatchers determine which thread(s) a coroutine runs on. Dispatchers.Main: Android main thread — for UI updates only. Dispatchers.IO: optimised for I/O operations (network, disk) — thread pool of 64 threads. Dispatchers.Default: CPU-intensive work (sorting, parsing) — pool of CPU core count threads. Dispatchers.Unconfined: resumes in whatever thread the caller was on — rarely used outside testing. Switch with withContext(Dispatchers.IO) {} inside a coroutine.
MediumQ28. What is the difference between StateFlow and SharedFlow?
Strong answer: StateFlow: always has a value (initial value required), new collectors immediately receive the current value, replays the last value, suitable for UI state. SharedFlow: configurable replay (0 by default), no initial value required, suitable for events (navigation events, snackbar messages). Key use case distinction: StateFlow for “what is the current state” (e.g., loading, error, success), SharedFlow for “something happened once” (e.g., show a toast, navigate to screen). Using StateFlow for events causes re-processing on rotation.
AdvancedQ29. How do you handle exceptions in Kotlin Coroutines? What is a SupervisorJob?
Strong answer: In regular coroutines, an unhandled exception in a child cancels the parent and all siblings (bidirectional failure propagation). SupervisorJob changes this — a child failure does NOT cancel other children or the parent. Use supervisorScope {} or CoroutineScope(SupervisorJob())} for independent tasks where one failure shouldn’t kill others (e.g., loading multiple data sources in parallel). Catch exceptions with try/catch inside the coroutine or with CoroutineExceptionHandler on the scope. Note: async exceptions are deferred until .await() is called.
AdvancedQ30. Explain Flow operators: map, filter, flatMapLatest, combine, zip. When do you use flatMapLatest?
Strong answer:map/filter: transform/filter each emission. combine: combines latest values from multiple flows (emits when ANY flow emits). zip: pairs emissions from two flows (emits only when BOTH emit, in order). flatMapLatest: on each upstream emission, starts a new inner flow and CANCELS the previous one — perfect for search-as-you-type where you want only the latest query’s results, discarding in-flight requests for previous queries. flatMapMerge would run all concurrently; flatMapConcat would queue them — flatMapLatest cancels obsolete work.
Section 5: Performance & Testing (Q31–Q36)
MediumQ31. How do you profile an Android app that is dropping frames?
Strong answer: Tool chain: (1) Android Studio Profiler → CPU profiler in System Trace mode to see frame timing and thread activity. (2) Perfetto for detailed system-level tracing. (3) Developer Options → GPU rendering profile on device for quick visual. Common causes: main thread I/O (use Strict Mode to detect), expensive measure/layout passes (Hierarchy Viewer to detect deep nesting), unnecessary recompositions in Compose (Layout Inspector with recomposition counts), bitmap loading on main thread. Fix: move work off main thread, use RecyclerView/LazyColumn correctly, cache computations.
MediumQ32. What is App Startup library and how does it improve launch time?
Strong answer: Jetpack App Startup provides a single ContentProvider for initialising multiple libraries at app start, instead of each library registering its own ContentProvider (each ContentProvider has cold start overhead). Components implement Initializer<T> interface, declare dependencies on other initialisers. Also consider: lazy initialisation (don’t initialise what isn’t needed at launch), baseline profiles (pre-compile hot code paths), and using Startup trace to identify slow initialisers.
MediumQ33. What is the difference between unit tests, integration tests, and UI tests in Android?
Strong answer: Unit tests (JUnit, Mockito/MockK): test a single class in isolation, run on JVM without emulator, fast. Integration tests (Robolectric or with Hilt): test interaction between classes, may include Android framework components. UI/Instrumentation tests (Espresso for Views, Compose test APIs): run on emulator/device, test actual UI interactions, slow. Strategy: follow the testing pyramid — many unit tests, some integration tests, few UI tests. ViewModels and Repositories should be unit-testable without Android framework. Compose UI tests use createComposeRule() and can run on JVM with Robolectric.
MediumQ34. How do you test a ViewModel with coroutines?
Strong answer: Use kotlinx-coroutines-test: replace Dispatchers.Main with StandardTestDispatcher using Dispatchers.setMain(testDispatcher) in @Before. Use runTest {} block which controls virtual time. For testing StateFlow: use Turbine library to collect emissions and assert them in order. For for testing ViewModels: inject fake/mock Repository, trigger actions, assert resulting state. advanceUntilIdle() runs all pending coroutines.
AdvancedQ35. What are Android Baseline Profiles and how do they reduce startup time?
Strong answer: Baseline Profiles are a set of critical user journeys defined as rules that tell the Android runtime which classes and methods to pre-compile ahead-of-time (AoT). Normally, the ART JIT compiler compiles code lazily (on first use). With Baseline Profiles, hot paths are compiled at install time, reducing cold start by up to 40% and improving frame timing for initial interactions. Create with Macrobenchmark library, generate with BaselineProfileRule, include the baseline-prof.txt in the release build. Google Play uses it during APK installation.
AdvancedQ36. How do you reduce APK size and what are the key tools?
Strong answer: (1) Enable R8 (minification + obfuscation + shrinking) in release builds — removes unused code and renames identifiers. (2) Use Android App Bundle (AAB) instead of APK — Google Play delivers per-device APKs with only needed resources/ABIs. (3) resConfigs to include only supported languages. (4) WebP for images instead of PNG. (5) Use vector drawables for icons rather than density-specific PNGs. (6) Analyse APK with Android Studio’s APK Analyser — identify large dependencies. (7) Use debugImplementation and releaseImplementation correctly. Typical savings: 20–40% smaller deliverable via AAB.
Section 6: Modern Android & Android 14+ (Q37–Q40)
MediumQ37. What are the most important changes in Android 14 that affect existing apps?
Strong answer: Key Android 14 impacts: (1) Photo/video library permissions split into granular access (READ_MEDIA_IMAGES, READ_MEDIA_VIDEO separately), plus new partial access permission. (2) Foreground service types now required — must declare the type (dataSync, mediaPlayback, etc.) or face rejection. (3) Exact alarm permission (SCHEDULE_EXACT_ALARM) restricted by default. (4) Minimum targetSdkVersion enforcement (apps targeting below SDK 23 blocked). (5) Dynamic colours (Material You) stable and widely adopted. (6) Predictive back gesture API — apps must opt in and handle the new system animation.
MediumQ38. What is Compose Multiplatform and what does it mean for Android developers?
Strong answer: Compose Multiplatform (by JetBrains) extends Jetpack Compose to iOS, Desktop (macOS, Windows, Linux), and Web. Android developers can share UI code across platforms using Kotlin Multiplatform (KMP). The Android-specific Compose APIs (Context, resources, navigation) need platform abstraction, but the core composable UI code is shared. In 2026, Compose Multiplatform iOS is stable. This matters for Android developers because KMP+CMP is becoming a preferred alternative to Flutter/React Native for teams that want to share business logic and UI without learning Dart or JavaScript.
AdvancedQ39. How does WorkManager differ from a Foreground Service? When do you use each?
Strong answer: WorkManager: for deferrable, guaranteed background work that can be delayed and retried. It respects battery optimisations (Doze mode), survives app restart and device reboot, chains tasks. Foreground Service: for work that must run NOW without interruption, with a visible notification (media playback, file download the user is waiting for). Key distinction: WorkManager work can be deferred; Foreground Service work cannot. In 2026, Foreground Services face more restrictions (type declaration required, background start restrictions). Use WorkManager first; only use Foreground Service when the work is truly time-sensitive and user-visible.
AdvancedQ40. A senior Android developer question: How would you architect a real-time chat feature with offline support?
Strong answer: Architecture: (1) WebSocket/SSE connection managed by a Foreground Service for real-time message reception. (2) All received messages written to Room database immediately. (3) UI observes Room Flow — in never reads directly from network. (4) Sent messages written to Room first with “pending” status, then uploaded to server; status updated on success/failure. (5) WorkManager handles retry logic for failed sends with exponential backoff. (6) Reconnection logic in the service with exponential backoff + network callback. (7) For initial load and pagination: Paging 3 with RemoteMediator. This gives true offline-first behaviour with eventual consistency.
✓ Hiring Tip: Structure Your Interviews by Seniority
Junior (0–2 yrs): Focus on Q1–Q5, Q9–Q10, Q17, Q25–Q26. Mid-level (3–5 yrs): Q6–Q12, Q18–Q21, Q27–Q29, Q31–Q34. Senior (5+ yrs): Q13–Q16, Q22–Q24, Q29–Q30, Q35–Q40. A senior developer who can’t answer Q39–Q40 with a real production architecture has not shipped a complex Android app.
Hire a Vetted Android Developer — Kotlin & Compose Ready
GetDeveloper’s Android developers are assessed on Jetpack Compose, Kotlin Coroutines, MVVM/MVI architecture, and performance profiling — the skills that matter in 2026 production apps.