Ryz Labs/Interview questions/Android developer
Interview questions

Android interview questions for senior developers (2026)

Platform-level Android questions for senior hires: lifecycle and process death, Compose, ViewModel, WorkManager, Room, Gradle builds, startup, R8 and Play release.

These Android interview questions test whether an engineer can build, ship and keep a production Android app healthy. They cover activity and process lifecycle, Jetpack Compose, ViewModel and lifecycle-aware state, WorkManager, Room, Gradle builds, app startup, R8 and releasing through Google Play. Kotlin language questions (coroutines internals, generics, sealed hierarchies, K2) live in our separate Kotlin set, so this page stays with the platform. At Ryz, recruiters source senior Android engineers based on apps they have shipped and maintained, candidates then complete structured NTRVSTA AI interviews on topics like these, and recruiters review them before and after. AI scores are advisory. People decide who moves forward.

How to use these questions

Android punishes engineers who only test on one fast phone. Pick questions that expose real-device experience: process death, background limits, build times, startup and crash triage. For mid-level roles, focus on fundamentals and intermediate; for senior roles, include at least two from performance and release and two from architecture.

Ask candidates to describe a specific bug they fixed for each topic, then probe what they measured. Finish with the exercise below, which shows how they structure a Compose screen with real state.

Fundamentals

What is the difference between a configuration change and process death, and what survives each?

On rotation or theme change the activity is recreated but the process stays: ViewModel instances survive, local remember state does not. When the system kills a backgrounded process, everything in memory is gone, and only saved instance state (SavedStateHandle, rememberSaveable) and persisted data come back. The user returns to the same screen expecting the same state.

What a strong answer shows: They know ViewModel does not survive process death and test it deliberately.

How do you reproduce and test process death?

Put the app in the background and run adb shell am kill <package>, then return from recents. The "Don't keep activities" developer option only tests activity destruction, not process death. In tests, SavedStateHandle can be constructed directly with values to simulate restoration.

What a strong answer shows: A repeatable method and the distinction between the two developer tools.

Explain remember, rememberSaveable and state hoisting in Compose.

remember keeps a value across recompositions; rememberSaveable also survives configuration changes and process death through the saved-state bundle. Hoisting moves state up to the caller so the composable becomes stateless: it receives a value and an event callback, which makes it reusable and previewable.

@Composable
fun SearchBar(query: String, onQueryChange: (String) -> Unit) {
TextField(value = query, onValueChange = onQueryChange)
}

@Composable
fun SearchScreen(vm: SearchViewModel = hiltViewModel()) {
val state by vm.uiState.collectAsStateWithLifecycle()
SearchBar(query = state.query, onQueryChange = vm::onQueryChange)
}

What a strong answer shows: Clear rules about where state lives and why screen-level state belongs in a ViewModel.

Why use collectAsStateWithLifecycle instead of collectAsState?

collectAsState keeps collecting while the composable is in composition, even when the app is in the background. collectAsStateWithLifecycle stops collecting below a lifecycle state (STARTED by default), so upstream location updates or database queries stop when the user leaves.

What a strong answer shows: They think about battery and work done while invisible.

How do Android App Links differ from plain deep links?

A deep link is any intent filter for a URI; the system may show a chooser. App Links are verified https links: the domain hosts /.well-known/assetlinks.json with the app's signing certificate fingerprint, and the filter sets autoVerify. Verification fails if the fingerprint is from the upload key instead of the Play App Signing key.

What a strong answer shows: They have debugged verification and know which certificate Play actually signs with.

How do you handle runtime permissions on recent Android versions?

Request at the moment of use with the Activity Result API, explain why if the user declined once, and degrade gracefully. Android 13 requires POST_NOTIFICATIONS at runtime. For media, prefer the photo picker, which needs no storage permission at all, and request partial media access where it fits.

What a strong answer shows: Permission requests as a product decision, and preference for APIs that avoid them.

What causes a Context leak, and how do you find one?

Holding an Activity or View in something that outlives it: a singleton, a static field, a long-running callback or a coroutine scope not tied to the lifecycle. Use the application context for long-lived objects. LeakCanary detects retained activities in debug builds and shows the reference chain.

What a strong answer shows: They can read a leak trace and fix the reference that matters.

Intermediate

How do you set up Room for an offline-first list and test migrations?

Expose queries as Flow so the UI updates when data changes, write network results into Room, and let the UI read only from the database. Export schemas and test every migration with MigrationTestHelper, or use auto-migrations for simple changes.

@Dao
interface OrderDao {
@Query("SELECT * FROM orders WHERE status = :status ORDER BY createdAt DESC")
fun observeByStatus(status: String): Flow<List<OrderEntity>>

@Upsert
suspend fun upsertAll(orders: List<OrderEntity>)
}

What a strong answer shows: A single source of truth and migrations tested before users hit them.

WorkManager, a foreground service or a coroutine in a ViewModel: how do you choose?

Work tied to the screen goes in viewModelScope. Deferrable work that must run even if the app dies (uploads, sync) goes to WorkManager with constraints. Long-running user-visible work such as navigation or media playback needs a foreground service, and since Android 14 it must declare a foreground service type with the matching permission.

val request = OneTimeWorkRequestBuilder<UploadWorker>()
.setConstraints(Constraints(requiredNetworkType = NetworkType.CONNECTED))
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
.build()
WorkManager.getInstance(context)
.enqueueUniqueWork("upload-$id", ExistingWorkPolicy.KEEP, request)

What a strong answer shows: They match guarantees to the job and use unique work to avoid duplicates.

How do you structure navigation in a Compose app?

Navigation Compose with type-safe routes (serializable route classes, Navigation 2.8 and later) gives compile-checked arguments. Pass IDs, not objects, and load data in the destination's ViewModel. Newer Compose-first apps may adopt Navigation 3, which models the back stack as state you own.

What a strong answer shows: Minimal arguments, testable navigation, and awareness of where the libraries are going.

How does Hilt scoping work, and when have scopes caused bugs?

Components follow lifecycles: SingletonComponent, ActivityRetainedComponent, ViewModelComponent and so on. A binding scoped to a component is one instance per component. Bugs come from singleton-scoping something that holds user data (it leaks across logout) or forgetting a scope so a cache is recreated on every injection.

What a strong answer shows: They map scope to data lifetime, including logout.

What does unidirectional data flow look like on an Android screen?

The ViewModel exposes one immutable UI state (a data class in a StateFlow), the UI renders it and sends events back as function calls, and the ViewModel updates state from repositories. One-off effects like navigation are modelled as state that the UI consumes and clears, rather than fire-and-forget events that get lost during configuration changes.

What a strong answer shows: A clear contract between UI and logic, and a considered answer on one-off events.

A clean build takes 12 minutes and incremental builds four. How do you speed it up?

Measure with a build scan or the Build Analyzer. Typical wins: enable the configuration cache and build cache, replace kapt with KSP, split the app into modules so changes recompile less, move shared build logic into convention plugins, use version catalogs, and stop non-cacheable custom tasks from running every build.

What a strong answer shows: Data first, then Gradle-specific fixes, not just "buy faster laptops".

Performance, startup and release

A Compose list recomposes far more often than expected. How do you investigate?

Use Layout Inspector recomposition counts and composition tracing in a release-like build. Common causes: reading fast-changing state too high in the tree, unstable parameter types, new lambdas or lists created each recomposition, and missing key in lazy lists. Strong skipping mode (default in recent Compose compiler versions) helps, but reading state late, for example in a lambda modifier, still matters.

// Reads scroll offset during layout, not composition
Box(Modifier.offset { IntOffset(0, -listState.firstVisibleItemScrollOffset) })

val showButton by remember {
derivedStateOf { listState.firstVisibleItemIndex > 0 }
}

What a strong answer shows: They know Compose's phases and can defer state reads instead of guessing.

Cold start is slow. What do you measure and change?

Measure time to initial display and to full display with Macrobenchmark on a real mid-range device. Remove eager initialization from Application.onCreate (lazy SDK setup, App Startup library where useful), avoid disk I/O on the main thread, and ship Baseline Profiles so critical paths are precompiled at install time.

What a strong answer shows: Field and lab measurement plus Baseline Profiles, which are among the cheapest real wins.

After enabling R8 full mode, the release build crashes on JSON parsing. Why?

R8 removed or renamed classes and fields it saw no direct usage of, and a reflection-based parser such as Gson needs them. Fix with targeted keep rules for the model classes, or move to a code-generating library like kotlinx.serialization or Moshi codegen. Upload the mapping file to Play and crash tools so stack traces are deobfuscated.

What a strong answer shows: They understand shrinking and reflection, and test minified builds before release.

How do you find and fix ANRs?

Play Console's Android vitals groups ANRs with main-thread stack traces. Typical causes: disk or network I/O on the main thread, lock contention, slow broadcast receivers and heavy work in onCreate. StrictMode in debug builds catches main-thread I/O early. ApplicationExitInfo lets the app read why its previous process ended.

What a strong answer shows: Production data, not guesswork, and knowledge that vitals affect Play visibility.

Walk through a safe Play release.

Upload an Android App Bundle signed with the upload key, with Play App Signing holding the app key. Release to internal and closed testing, then a staged rollout to a small percentage, watching crash and ANR rates. Halt the rollout if vitals regress, and gate risky features behind remote flags so you can turn them off without a new build.

What a strong answer shows: Staged rollouts with clear halt criteria, and knowledge that Play cannot roll back a published version.

After raising targetSdk to 35, content draws behind the status bar. What happened?

Android 15 enforces edge-to-edge for apps targeting API 35. The app must handle window insets itself: enableEdgeToEdge() in the activity, and in Compose apply WindowInsets.safeDrawing or Scaffold's inner padding. Every targetSdk bump needs a review of behavior changes for that level, not just a version change.

What a strong answer shows: They read behavior-change notes and treat targetSdk upgrades as real work.

Senior and architecture

How would you modularize an app for 30 Android engineers?

Feature modules that depend on core modules (design system, networking, data) but not on each other, with navigation contracts or API modules between features. Convention plugins keep build config consistent. Measure the effect on build times and avoid a giant :common module that everything depends on.

What a strong answer shows: They balance build speed, ownership and dependency rules, and can say how they enforce them.

Design offline-first sync for a field-service app used in poor coverage.

Room as the source of truth, local writes recorded in an outbox table, and a WorkManager job that pushes the outbox with network constraints and exponential backoff. Requests carry idempotency keys so retries are safe. Pull changes since a server cursor, resolve conflicts with versions per record, and show sync state in the UI.

What a strong answer shows: Idempotency, conflict policy and user visibility, not just "cache the responses".

How do you migrate a large View-based app to Compose?

Start with new screens and leaf components, use ComposeView inside existing layouts and AndroidView for views Compose lacks. Build a Compose theme that maps the existing design tokens first so screens look identical. Keep fragment navigation until enough screens are migrated, and watch performance in mixed hierarchies.

What a strong answer shows: An incremental path that keeps shipping features.

How do you support tablets and foldables properly?

Use window size classes to switch layouts (list-detail on wide screens), handle resizing and multi-window, and avoid locking orientation. Recent Android versions increasingly ignore orientation and resizability restrictions on large screens, so apps that assumed portrait phones break. Test on the resizable emulator and a foldable.

What a strong answer shows: Adaptive layouts as a requirement rather than an afterthought.

How do you protect sensitive data and your backend from tampered clients?

Keep secrets on the server. Store tokens with keys from the Android Keystore (the Jetpack security-crypto library is deprecated, so teams use Keystore directly or DataStore with their own encryption). Use the Play Integrity API so the backend can check app and device integrity, configure network security config to block cleartext, and pin certificates only with a rotation plan.

What a strong answer shows: Server-side trust decisions and current knowledge of the security libraries.

What does a good Android test setup look like?

Fast JVM unit tests for ViewModels and repositories with fakes, Robolectric or Compose UI tests for screen behavior, screenshot tests for the design system, and a few end-to-end instrumented tests on Gradle Managed Devices in CI. Flaky tests get quarantined and fixed, not retried forever.

What a strong answer shows: A deliberate split by speed and confidence, with CI that stays trusted.

How do you choose minSdk and keep using new Java APIs on old devices?

Look at your own install base, Play's distribution data and what higher API levels remove (compat code, workarounds). Core library desugaring lets you use java.time and other newer APIs on older devices. Raise minSdk on a planned cadence and communicate it to product early.

What a strong answer shows: A decision balancing users and engineering cost, plus knowledge of desugaring.

Red flags to watch for

A practical exercise

Run a 90-minute pairing session on a starter project: a Compose app with a list of orders from a fake API. The candidate adds offline caching with Room, pull to refresh, a filter that survives process death, and a retry that uses WorkManager when the device is offline. Provide Hilt wiring and a fake network that can fail on demand.

Hire senior Android developers vetted with these questions

When you need people rather than a question list, hire senior Android developers through Ryz. You get the top 1% of the candidates we interview, on your team and in your repos and release train, working within ±1h of US time zones and experienced with Compose, modular Gradle builds and Play releases. Our vetting process explains how they are screened, and our Android developer job description helps you define the role.

FAQ

Should Android interviews still cover the View system?

For most roles, briefly. New work is in Compose, but many production apps still have View-based screens, RecyclerView and fragments. Ask one interop question so you know the candidate can work in a mixed codebase.

How is an Android interview different from a Kotlin interview?

Android questions test the platform: lifecycle, background limits, Compose, builds and release. Kotlin questions test the language and coroutines in depth. A senior Android hire needs both, but backend Kotlin roles need almost none of the Android material.

How many questions fit in a one-hour Android interview?

Five or six with follow-ups. Spend more time on one scenario, such as a process death bug or a slow cold start, than on rapid-fire definitions. Depth on one real problem predicts on-the-job performance better than breadth.

Questions we didn't answer? Email info@ryzlabs.com.

Explore Ryz Labs

Staff augmentationDedicated development teamsAI pod teamsForward deployed engineersNearshore software developmentAI engineering teamsHire engineers by roleRyz Labs vs competitorsAlternatives guidesBuyer guidesCase studiesHow we vet engineers
Ryz Labs

Senior engineers in your time zone. AI pod teams that ship.

Tell us who you need. You'll get a scoped plan, a price and the names of the people who would do the work.

Start a conversation →