Android App Development Interview Questions for 1 Year Experience
40 questions with answers for candidates with 1 year of experience — 19 easy, 21 medium.
What interviewers expect: 1 year experience
With one year of experience, interviewers expect you to have shipped features in a real app. Questions cover MVVM, coroutines, Retrofit, Room and lifecycle bugs you have fixed. You may get a take-home task or live coding of a small screen with a ViewModel. Explain your exact contribution clearly rather than describing the whole team's work.
This set is weighted towards Jetpack Compose (8), Kotlin Fundamentals (8), Room, DataStore and WorkManager (6), App Architecture, MVVM and Hilt (5).
MVVM and ViewModel state Coroutines and viewModelScope Retrofit and error handling Debugging real crashes
Solved at this levelStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
✓ 3 questions free • Unlock the rest in seconds
01
Unit tests for a ViewModel are hard to write because it creates the repository inside itself. What do you change?
Constructor-inject the repository as an interface, so tests can pass a fake implementation. With Hilt, add @Inject to the constructor and bind the real implementation in a module. Also inject dispatchers rather than using Dispatchers.IO directly. Then tests create ViewModel(FakeRepository()) and control results precisely. This change improves design too, because the ViewModel no longer knows how data is created.
02
Write a unit test for a ViewModel using a fake repository and runTest.
class ProductViewModelTest {
@get:Rule val mainRule = MainDispatcherRule()
@Test
fun loadsProductsIntoState() = runTest {
val fake = FakeProductRepository(listOf(Product("1", "Pen", 10.0)))
val vm = ProductViewModel(fake)
advanceUntilIdle()
val state = vm.state.value
assertFalse(state.loading)
assertEquals(1, state.items.size)
assertNull(state.error)
}
}
03
How do you share data between two screens in a checkout flow?
Options in order of preference: pass small IDs as navigation arguments and load the rest from a repository; store flow data in a repository scoped to the session; or use a ViewModel scoped to a nested navigation graph so all checkout screens share it and it is cleared when the flow ends. Avoid activity-scoped ViewModels holding everything, and avoid passing large objects through arguments.
An ANR (Application Not Responding) happens when the main thread is blocked, for example an input event not handled within about 5 seconds or a BroadcastReceiver taking too long. Causes include disk or network work on the main thread, heavy computation, lock contention and slow SharedPreferences commits. Prevent ANRs by moving work to background dispatchers, using StrictMode in debug builds, and tracking ANR rates in Android vitals.
05
What is the difference between Parcelable and Serializable?
Serializable is a Java marker interface that uses reflection and creates many temporary objects, so it is slower. Parcelable is Android-specific and writes fields explicitly to a Parcel, which is much faster for IPC. Writing Parcelable by hand is verbose, but the kotlin-parcelize plugin with @Parcelize generates it. Use Parcelable for Bundles and intents, and never use either as a long-term storage format.
State hoisting means moving state out of a composable to its caller, and passing the value plus event callbacks down, like value and onValueChange. The composable becomes stateless, reusable and easy to preview and test. State should be hoisted to the lowest common parent that needs to read or change it, and screen-level state usually lives in a ViewModel. This creates unidirectional data flow: state flows down, events flow up.
07
A Room query on a large table is slow and causes jank. How do you fix it?
Make sure the query runs off the main thread using suspend or Flow. Use EXPLAIN QUERY PLAN to check for full table scans, and add an @Index on columns used in WHERE, JOIN or ORDER BY. Select only needed columns into a smaller data class instead of SELECT *, page results with Paging 3, and avoid loading large blobs into lists. Wrap bulk inserts in a single transaction.
Use try-catch around suspend calls for expected failures like network errors; this is the clearest approach. A CoroutineExceptionHandler on a root launch catches uncaught exceptions as a last resort for logging, but it does not work for async or child coroutines. For async, exceptions surface at await. Always rethrow CancellationException. Many teams wrap results in a Result or sealed type so the UI layer gets an explicit error state.
Create an in-memory database with Room.inMemoryDatabaseBuilder in an instrumented test or with Robolectric, get the DAO, insert test data and assert query results. For Flow queries, collect the first value with first() or use Turbine. Close the database in @After. Test edge cases like conflict strategies and ordering. In-memory databases are fast and isolated, so each test starts clean.
When you change the schema, increase the database version and provide a Migration(from, to) with SQL like ALTER TABLE, or use AutoMigration for simple changes with exported schemas. Without a migration, Room throws at startup unless you call fallbackToDestructiveMigration, which wipes user data. Export schemas to version control and test migrations with MigrationTestHelper so upgrades from every old version are verified.
11
Write code to request camera permission using the Activity Result API.
Use lifecycleScope.launch with repeatOnLifecycle(Lifecycle.State.STARTED) so collection starts when the UI is visible and stops in the background, avoiding wasted work and crashes from updating views that are not ready. In a Fragment, use viewLifecycleOwner.lifecycleScope because the fragment view can be destroyed while the fragment lives. In Compose, use collectAsStateWithLifecycle(), which does the same lifecycle-aware collection.
14
What is a use case in the domain layer and when is it worth adding?
A use case is a class with one job, like GetDiscountedCartTotalUseCase, typically exposing operator fun invoke. It holds business logic that combines repositories or is reused by several ViewModels. It keeps ViewModels thin and logic testable. For simple apps where the ViewModel only calls one repository method, use cases add boilerplate without benefit, so the domain layer is optional in the official guidance.
15
How do you store relationships in Room, such as orders with items?
Create separate entities and link them with a foreign key column, like OrderItem.orderId. Then define a data class with @Embedded order and @Relation(parentColumn = "id", entityColumn = "orderId") items, and query it in a DAO method annotated with @Transaction so both reads are consistent. For many-to-many relations, add a cross-reference entity with junction. Add indices on foreign key columns for query speed.
16
How does Compose interoperate with existing XML views?
To use Compose inside views, add a ComposeView to an XML layout or Fragment and call setContent, setting a ViewCompositionStrategy suited to the lifecycle. To use a view inside Compose, wrap it with AndroidView(factory = { MapView(it) }, update = { }). This allows migrating screen by screen, starting with new screens or leaf components. Share the theme using a Compose theme adapter or by mirroring tokens.
17
A screen shows a snackbar again every time the device rotates. How do you fix it?
The event is probably stored as state in a StateFlow or LiveData, so it is re-delivered when the new Activity collects again. Options: model it as part of UI state and have the UI call a function like onMessageShown() to clear it after display, which is the approach the official guidance recommends; or send it through a Channel exposed with receiveAsFlow so each event is consumed once. Collect with lifecycle awareness to avoid losing events.
18
Tell me about a time you learned a new language or framework quickly for a project.
Situation: my team switched a feature to Kotlin when I only knew Java. Task: deliver a profile screen in two weeks. Action: I spent the first evenings on the official Kotlin koans, converted small utility classes first, and asked a senior to review my early pull requests for idiomatic usage. Result: the screen shipped on time, and my later code needed far fewer review comments. I now learn by building something small immediately.
19
How do default and named arguments reduce the need for overloads?
Kotlin functions can define default values, such as fun showToast(msg: String, long: Boolean = false), so callers skip arguments they do not need. Named arguments, like showToast(msg = "Saved", long = true), make calls readable and let you change argument order. Together they replace telescoping constructors and builder patterns in many cases. For Java callers, add @JvmOverloads so overloads are generated automatically.
Kotlin's type system separates nullable types (String?) from non-nullable types (String). The compiler stops you calling methods on a nullable value without a check. You use the safe call operator ?., the Elvis operator ?: for defaults, smart casts after null checks, or !! to force a value and risk a NullPointerException. This moves most null crashes from runtime to compile time, which is a major reason Android adopted Kotlin.
21
What does derivedStateOf do and when should you use it?
derivedStateOf creates state computed from other state, and only triggers recomposition when the computed result changes. A classic example is showing a scroll-to-top button: val showButton by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } }. Without it, every scroll pixel would recompose. Do not use it when the result changes as often as the input, because it only adds overhead.
22
What are higher-order functions and lambdas in Kotlin?
A higher-order function takes a function as a parameter or returns one. A lambda is an anonymous function literal like { x: Int -> x * 2 }. Collection operations such as map, filter and forEach are higher-order functions. In Android you see them in click listeners, Compose callbacks like onClick = { } and coroutine builders. If the last parameter is a function, the lambda can be written outside the parentheses, known as trailing lambda syntax.
23
How do you build a single-activity app with the Navigation component?
Keep one Activity hosting a NavHost, and represent each screen as a Compose destination or Fragment. Define routes in a navigation graph, preferably with type-safe routes using Kotlin serialization in Navigation 2.8 and later. Use nested graphs for flows like login or checkout, pop the back stack with popUpTo when finishing a flow, and handle deep links on destinations. This simplifies shared transitions and state handling.
24
How do you handle one-time navigation after a successful login in MVVM?
Recommended practice is to reflect the result in state, such as isLoggedIn = true in UiState, and have the UI react with LaunchedEffect(state.isLoggedIn) to navigate, then the ViewModel marks it consumed if needed. An alternative is a Channel exposed as Flow and collected with lifecycle awareness. Avoid calling navigation directly from the ViewModel, because it should not reference NavController or views.
25
How do you collect a ViewModel StateFlow in a composable?
Use val state by viewModel.uiState.collectAsStateWithLifecycle() from lifecycle-runtime-compose. It collects only while the lifecycle is at least STARTED, saving resources in the background, unlike collectAsState which keeps collecting. Pass the resulting state and callbacks to a stateless screen composable, which keeps the screen previewable and testable without a ViewModel. Obtain the ViewModel with hiltViewModel() or viewModel() at the route level.
26
Why should we hire you as a fresher Android developer?
Give evidence, not adjectives. For example: I have built and published two apps using Kotlin and Jetpack Compose, one with Room and Retrofit and an MVVM structure, and both are on my GitHub with a README and tests. I debug by reading logs and stack traces before asking for help, and I am comfortable learning from code reviews. I can contribute to bug fixes and small features from the first month.
27
How do you model API results and errors cleanly for the UI?
Map transport-level outcomes into a sealed result type in the data layer, for example Success(data), HttpError(code, message) and NetworkError. Catch IOException for connectivity, HttpException for non-2xx codes, and serialization exceptions for bad payloads, and never catch CancellationException. The ViewModel converts these into user-friendly UiState messages. Keep DTOs separate from domain models so API changes do not ripple through the UI.
28
How do you prioritise when you have several bugs and a feature deadline in the same week?
I rank by user impact and risk: crashes and data loss first, then bugs affecting payments or login, then the feature, then minor UI issues. I share the plan with my lead and product manager so trade-offs are visible, and I ask for help or a scope cut early instead of quietly missing the deadline. Small, safe fixes I batch together to save release overhead.
29
Write a sealed interface for a screen state and handle it with when.
sealed interface UiState {
data object Loading : UiState
data class Success(val items: List<String>) : UiState
data class Error(val message: String) : UiState
}
fun render(state: UiState) = when (state) {
UiState.Loading -> showProgress()
is UiState.Success -> showList(state.items)
is UiState.Error -> showError(state.message)
}
30
What is the difference between val and var in Kotlin?
val declares a read-only reference that cannot be reassigned after initialisation, while var declares a mutable reference. Note that val does not make the object immutable: a val pointing to a MutableList can still have items added. Prefer val by default because it makes code easier to reason about and safer in concurrent code, and switch to var only when reassignment is genuinely needed.
31
How do runtime permissions work in modern Android?
Dangerous permissions like camera, location and notifications (POST_NOTIFICATIONS since Android 13) must be declared in the manifest and requested at runtime. Use the Activity Result API with RequestPermission or RequestMultiplePermissions contracts. Show a rationale when shouldShowRequestPermissionRationale returns true, handle denial gracefully, and direct users to settings if they deny permanently. Request permissions in context, at the moment the feature needs them.
32
What are extension functions and how are they resolved?
Extension functions let you add functions to an existing class without inheriting from it, for example fun String.isValidEmail(): Boolean. They are resolved statically at compile time based on the declared type of the variable, not the runtime type, so they do not support polymorphism. They cannot access private members of the class. They are used widely in Android KTX libraries, such as Context.toast or View.isVisible.
33
What is Jetpack Compose and how is it different from XML views?
Jetpack Compose is Android's modern declarative UI toolkit. You describe the UI as Kotlin functions of state, and Compose redraws the parts that change when state changes. With XML views you inflate layouts and imperatively update views with findViewById or binding. Compose removes adapters and much boilerplate, makes theming and previews easier, and interoperates with views through ComposeView and AndroidView, allowing gradual migration.
Give icons and images a meaningful contentDescription, or null when decorative. Keep touch targets at least 48dp, which Material components handle by default. Use semantics modifiers to merge descendants for list items, mark headings with heading(), and describe custom actions. Support font scaling by using sp and avoiding fixed heights for text. Check colour contrast and test with TalkBack and the Accessibility Scanner.
35
What is a ContentProvider and when would you build one?
A ContentProvider exposes data through a URI-based interface with query, insert, update and delete, and handles permissions for cross-app access. You need one when sharing data with other apps, providing search suggestions, or supporting widgets that read your data. FileProvider is a ready-made provider for sharing files securely with content URIs instead of file paths. For data used only inside your app, Room is simpler.
36
What is an Android App Bundle and why does Play require it?
An App Bundle (.aab) is a publishing format containing all compiled code and resources. The Play Store generates optimised APKs for each device configuration, delivering only the needed screen density, CPU architecture and language resources, which reduces download size. Since August 2021, new apps on Play must use AAB with Play App Signing, where the store holds the app signing key and you sign uploads with an upload key.
37
After an app update, users report that the app crashes on launch. Logs show a Room migration error. What do you do?
Immediately halt the staged rollout in Play Console. Reproduce by installing the previous version, adding data, and upgrading. The schema version was probably increased without a matching migration, or a migration SQL does not match the new entity. Write the correct Migration, test it with MigrationTestHelper from every supported old version, and release a hotfix. Avoid destructive fallback because it deletes user data.
38
Two screens show different values for the same cart count. How do you fix the architecture?
Each screen probably fetches and caches its own copy. Create a single CartRepository, scoped as a singleton, that owns cart data in Room or an in-memory StateFlow, and have both ViewModels observe its Flow instead of keeping private copies. Mutations go through the repository so every observer updates automatically. This enforces the single source of truth and removes the need for manual refresh calls between screens.
Modifiers are applied in a chain from left to right, each wrapping the next. Modifier.padding(16.dp).background(Color.Red) adds padding outside the red area, while background first then padding colours the padding too. Similarly, clickable before padding makes the whole padded area clickable. Thinking of each modifier as wrapping the content after it helps predict layout, size and touch target behaviour.
40
A composable makes an API call every time the screen recomposes. What is wrong and how do you fix it?
The call is placed directly in the composable body, which runs on every recomposition. Composables must be side-effect free. Move loading to the ViewModel, triggered in init or by a user event, and expose the result as StateFlow. If something must start from composition, wrap it in LaunchedEffect with a stable key like the item ID, so it runs once per key rather than on every recomposition.
Frequently asked questions
What is asked in Android App Development interviews for 1 year of experience?
With one year of experience, interviewers expect you to have shipped features in a real app. Questions cover MVVM, coroutines, Retrofit, Room and lifecycle bugs you have fixed. You may get a take-home task or live coding of a small screen with a ViewModel. Explain your exact contribution clearly rather than describing the whole team's work.
How many questions should I practise before a Android App Development interview?
Work through all 40 questions here, then the company-wise sets for the employers you are targeting. Quality matters more than count: be able to explain why each answer is right.
Which topics matter most for 1 year of experience?
Focus first on MVVM and ViewModel state, Coroutines and viewModelScope, Retrofit and error handling, Debugging real crashes.