Senior Android Developer Interview Questions and Answers

Senior Android developer interviews test whether you can ship reliable mobile features under real constraints—process death, flaky networks, battery limits, and large codebases—not whether you can name every Activity callback from memory. Interviewers at the senior level want trade-offs: why MVVM over MVI for your team, how you structure offline sync, and what you would cut from scope when the deadline is fixed.

Below are 40 senior Android developer interview questions grouped by Kotlin, lifecycle, Compose, coroutines, architecture, offline-first data, performance, testing, and system design. Open each answer after you try the question yourself. For the iOS side of mobile prep, see iOS developer interview questions. For JVM and concurrency fundamentals that underpin Kotlin, see Java interview questions (part 1) and part 2.

NOTE
Prep target: Senior loops often combine deep technical rounds with system design and behavioral stories. Narrate assumptions aloud—offline behavior, error states, and what happens when the OS kills your process. Use What interviewers are testing to understand the competency behind each question, then practise A strong answer is naturally in your own words.

Interview context and how to prepare

What extra bar do senior Android interviews add?

Mid-level screens check whether you can implement features. Senior screens check whether you can own them across teams and production edge cases.

Area Mid-level signal Senior signal
UI Build screens, handle rotation Compose performance, state ownership, accessibility
Data Call APIs, cache in Room Offline-first, sync conflicts, single source of truth
Concurrency Use viewModelScope.launch Structured concurrency, Flow backpressure, testing
Architecture Explain MVVM layers Justify MVVM vs MVI, module boundaries, migration
Delivery Fix bugs Incidents, rollout strategy, mentoring, tech debt trade-offs

Common senior-only rounds:

  • System design — feed, chat, checkout, or sync architecture on a whiteboard
  • Code review — spot leaks, wrong scope, or fragile state
  • Behavioral — STAR stories with metrics (crash rate, retention, latency)

What is a typical senior Android interview loop?

Round Duration Focus
Recruiter / hiring manager 30 min Background, stack, leadership scope
Kotlin / platform deep dive 45–60 min Language, lifecycle, coroutines, Compose
Architecture & code review 45–60 min MVVM/MVI, DI, modularization, past projects
Live coding 45–90 min Feature slice, pagination, or refactor exercise
System design 45–60 min Offline-first, sync, notifications, scale
Behavioral 30–45 min Ownership, conflict, incidents, mentoring

Some companies merge platform and architecture into one pair programming session on a take-home or shared repo.

What to bring: Two shipped apps you can whiteboard—data flow, testing strategy, and one hard bug or incident you fixed.

What is a realistic 4–6 week prep plan?

Week Focus Output
1 Kotlin idioms, null safety, sealed classes, extensions Explain 10 language features with examples
2 Lifecycle, process death, ViewModel, SavedState Trace rotation + kill + restore aloud
3 Compose state, side effects, stability Build one small screen with loading/error
4 Coroutines, Flow, Room, repository pattern Diagram UI → VM → repo → local/remote
5 System design: offline-first, pagination, push Whiteboard one feed or sync design

Daily habit: Pick one scenario—"user submits form on airplane mode"—and explain UI, domain, data, and recovery in under three minutes.

Do you need Jetpack Compose for senior roles in 2026?

Most greenfield Android teams expect Compose fluency or a credible migration plan. Legacy View/XML still exists in large apps, but interviewers use Compose questions to test modern state management.

Signal What interviewers hear
Compose-first team State hoisting, side effects, stability, testing
View-heavy codebase Migration strategy, interop, when not to rewrite
Weak answer "I only know XML" with no learning plan

You do not need to be a Compose library author—but you should explain recomposition, remember vs rememberSaveable, and where state lives.

How much Java do senior Android developers need?

Kotlin is the interview default for new code. Seniors still encounter Java in older modules, SDK samples, and stack traces.

Topic Kotlin focus Java tie-in
Nullability String?, smart casts Java platform types / annotations
Concurrency coroutines / Flow threads, executors, locks
Collections Kotlin APIs Java collection interoperability
Legacy code Kotlin-first new modules reading and debugging Java modules

When comparing coroutines with raw threads, explain that suspension can free the underlying thread while waiting for I/O, allowing many concurrent operations without one blocked thread per task. Kotlin implements suspend functions through compiler-generated state-machine machinery.


Kotlin language depth

Explain Kotlin null safety—how is it different from Java?

What interviewers are testing: Whether you design nullability at Java/API/storage boundaries rather than sprinkling !!.

Kotlin separates nullable and non-null types at compile time.

Type Meaning
String Cannot hold null
String? May hold null
?. Safe call — skips if null
?: Elvis — default if null
!! Assert non-null — throws NPE if wrong
kotlin
fun length(name: String?): Int =
    name?.length ?: 0

Senior follow-up: Prefer explicit null handling at boundaries (API, DB, intents). Avoid !! in production paths—use early return, requireNotNull, or checkNotNull with messages.

A strong answer is:

Kotlin encodes nullability in the type system. I use safe calls and Elvis at boundaries and treat !! as a code smell except in tests or proven invariants.

When do you use data class vs sealed class?

What interviewers are testing: Whether you distinguish value records from closed state/result hierarchies.

Both model structured data; the choice is about closed hierarchies vs flat records.

Construct Best for
data class Immutable DTOs, UI state snapshots, API models
sealed class / sealed interface Finite result types, UI events, navigation destinations
kotlin
sealed class UiState {
    data object Loading : UiState()
    data class Success(val items: List<Item>) : UiState()
    data class Error(val message: String) : UiState()
}

Compose tie-in: Sealed hierarchies make when exhaustive—compiler forces you to handle every branch.

A strong answer is:

data class for product-shaped records; sealed class when the set of outcomes is fixed and I want exhaustive when without an else branch.

What are inline functions and reified type parameters?

What interviewers are testing: Whether you understand what inlining changes and why erased generic type information can become available to reified parameters. inline asks the Kotlin compiler to inline the function body and eligible lambdas at call sites, which can reduce higher-order-function overhead and enables reified type parameters.

kotlin
inline fun <reified T> Gson.fromJson(json: String): T =
    fromJson(json, T::class.java)

Interview use cases:

  • Type-safe intent extras (inline fun <reified T> Bundle.getParcelable)
  • DSL builders
  • Performance-sensitive higher-order functions

Caution: Large inlined functions inflate bytecode—not every higher-order function should be inline.

A strong answer is:

inline plus reified lets me access T::class at runtime without passing Class objects—common for type-safe parsing and Android parcelable helpers.

Extension functions vs utility classes—when do you use each?

What interviewers are testing: Whether you use extensions for cohesive receiver-specific behavior without pretending they add polymorphic members.

Extensions add behavior to existing types without inheritance—idiomatic Kotlin for Android APIs.

Approach When
Extension Thin wrappers on framework types (Context, String, Flow)
Top-level function Pure helpers with no receiver
Class with static utils Rare in Kotlin—prefer extensions or objects
kotlin
fun String.isValidEmail(): Boolean =
    Patterns.EMAIL_ADDRESS.matcher(this).matches()

Keep extensions discoverable—group in StringExt.kt, avoid dumping unrelated helpers on Context.

A strong answer is:

Extensions keep call sites readable for Android framework types. I avoid god-object extension files and prefer cohesive *Ext.kt modules.


Activity, Fragment, and process lifecycle

Walk through Activity lifecycle—and what matters for seniors?

What interviewers are testing: Whether you connect lifecycle callbacks to state survival and cleanup—not recite every callback. Interviewers care less about reciting every callback and more about state survival and resource cleanup.

Callback Senior talking point
onCreate Initialize the new Activity instance; restore saved UI state where appropriate
onStart / onStop Visible but may not be interactive
onResume / onPause Foreground interaction; pause heavy work
onDestroy Called when the Activity is finishing or being destroyed for recreation; do not rely on it for process-death cleanup because the system may kill the process without calling it

Configuration change: Without ViewModel or rememberSaveable, rotation recreates the Activity—dropping in-memory state.

Process death: OS may kill your process under memory pressure. Persistent app data (Room, DataStore, files) survives; small restorable UI state uses SavedStateHandle or rememberSaveable; plain Activity fields do not.

A strong answer is:

I focus on what survives rotation vs process death. ViewModel survives configuration changes but not process death. After system-initiated process death, small restorable UI state can come from saved-state APIs, while durable application data comes from Room/DataStore/files.

What is the role of ViewModel—and what should NOT go in it?

What interviewers are testing: Whether you understand screen-level state ownership and lifetime, not treat ViewModel as a place for all logic. A ViewModel is retained while its ViewModelStoreOwner scope survives configuration changes. The enclosing owner—Activity, Fragment, navigation destination/graph, or other owner—determines lifetime.

Belongs in ViewModel:

  • UI state (StateFlow, UiState)
  • Calls to repositories / use cases
  • Coroutine work in viewModelScope

Does not belong:

  • Activity, Fragment, Context, or Resources references for convenience — Android architecture guidance discourages passing UI/context types into ViewModel and even discourages AndroidViewModel for new code
  • Navigation side effects without a clear pattern (prefer representing outcomes in UI state)
  • Android framework classes that tie to lifecycle shorter than the VM
kotlin
class FeedViewModel(
    private val repo: FeedRepository
) : ViewModel() {

    private val _state = MutableStateFlow<UiState>(UiState.Loading)
    val state: StateFlow<UiState> = _state.asStateFlow()

    fun load() = viewModelScope.launch {
        _state.value = UiState.Success(repo.getFeed())
    }
}

A strong answer is:

ViewModel holds UI state and orchestrates use cases across rotation. I keep Android UI types out and survive process death with SavedStateHandle or Room—not memory-only fields.

What is process death and how do you design for it?

What interviewers are testing: Whether you design for process death with SavedStateHandle and persistent storage—not in-memory-only state. Android may kill your entire process when memory is low. When the user returns, the app restarts—Activities and ViewModels are recreated.

Survives process death? Mechanism
Yes Room, DataStore, files
Small restorable UI state SavedStateHandle, rememberSaveable
No In-memory singletons, static caches, plain remember state

Senior pattern: Single source of truth in Room; UI observes Flow from DB. Network refresh updates DB—not a fragile in-memory list.

Test: For recreation testing, use "Don't keep activities" or explicit Activity recreation. For real process-death restoration, use tooling/tests that kill the app process while preserving the task/saved state, then relaunch/return to the task. Do not treat Activity destruction alone as process death.

A strong answer is:

Process death wipes memory. I persist user-visible state in Room or DataStore and treat in-memory caches as optional performance layers only.

Fragment vs single-Activity Compose—what do seniors need to know?

What interviewers are testing: Whether you can articulate Fragment/XML vs single-Activity Compose trade-offs and migration without duplicating business logic.

Modern apps often use single-Activity + Compose Navigation. Legacy apps use multi-Fragment navigation.

Approach Trade-off
Fragments + XML Mature back stack, lots of existing code
Single Activity + Compose Simpler hosting, declarative UI, shared transitions
Hybrid AndroidViewBinding / ComposeView interop during migration

Interviewers want a migration story: new features in Compose, shared navigation graph, avoid duplicating business logic in both View and Compose layers.

A strong answer is:

I know Fragment back-stack rules for legacy code. For greenfield I prefer single-Activity Compose with Navigation-Compose and keep business logic in ViewModels and repositories either way.


Jetpack Compose

What are the three phases of Jetpack Compose?

What interviewers are testing: Whether you can connect composition, layout, and draw invalidation to performance diagnosis. Compose rendering is organized into three main phases: composition, layout, and draw. Depending on what state changes and where it is read, Compose may need to rerun only the affected phase(s).

Phase What happens
Composition Build or update the UI tree (run @Composable functions)
Layout Measure and place nodes
Draw Paint pixels

Performance angle: State can invalidate composition, layout, or draw depending on where it is read. Reduce work in the phase that is actually hot; for rapidly changing visual values, deferring reads to layout/draw can sometimes avoid unnecessary recomposition.

A strong answer is:

Composition decides structure, layout measures/places it, and draw renders pixels. I profile which phase is doing excess work rather than assuming recomposition is always the bottleneck.

What triggers recomposition—and how do you reduce it?

What interviewers are testing: Whether you understand state-read scopes and stability, not simply "avoid recomposition." Recomposition runs when state read during composition changes.

Reduction tactics:

Technique Purpose
Stable / immutable models Compiler can skip unchanged composables
remember Retain a value across recompositions while that composition remains alive
derivedStateOf Expose derived state and avoid downstream recomposition when the derived result itself has not changed
Lambda modifiers Defer state reads to layout/draw when possible
Keys in lists Correct identity for LazyColumn items
kotlin
val listState = rememberLazyListState()
val showFab by remember {
    derivedStateOf { listState.firstVisibleItemIndex > 0 }
}

A strong answer is:

Recomposition follows state reads. I stabilize models, hoist state, use derivedStateOf for derived UI flags, and profile with Layout Inspector when lists jank.

Explain state hoisting with a concrete example.

What interviewers are testing: Whether you place state at the right owner while keeping reusable composables stateless where useful.

State hoisting moves state up to the lowest common owner so child composables stay stateless and reusable.

kotlin
@Composable
fun SearchScreen(viewModel: SearchViewModel = hiltViewModel()) {
    val query by viewModel.query.collectAsStateWithLifecycle()
    SearchContent(
        query = query,
        onQueryChange = viewModel::onQueryChange
    )
}

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

Rule: State flows down as parameters; events flow up as callbacks.

A strong answer is:

I keep leaf composables stateless—state lives in ViewModel or parent, children receive value plus event callbacks. That improves preview and testability.

remember vs rememberSaveable—when do you use each?

What interviewers are testing: Whether you choose the right state survival API for composition, recreation, and process death.

API Survives
remember Recomposition while that composition remains alive
rememberSaveable Recomposition + activity recreation + supported system-initiated process recreation
ViewModel Configuration changes, but not process death by itself
SavedStateHandle Small state needed to restore after process death

Use rememberSaveable for small UI state—scroll position, expanded flags, draft text—not large lists (use Room).

Current Compose also provides rememberSerializable, which has similar saved-state lifetime semantics to rememberSaveable but uses Kotlinx Serialization for serializable custom types. Use it when that serialization model fits; keep saved state small either way.

Parcelable / list savers exist for custom types—mind Bundle size limits.

A strong answer is:

remember survives recomposition only while that composition remains alive. rememberSaveable and SavedStateHandle handle small UI state across recreation/process death; large data belongs in Room or DataStore.

LaunchedEffect vs DisposableEffect vs SideEffect?

What interviewers are testing: Whether you select an Effect API based on lifecycle and cleanup semantics. Compose side-effect APIs tie work to the composition lifecycle:

API Use when
LaunchedEffect(key) Suspend work tied to keys—animation, snackbar, lifecycle-tied collection when collection belongs in the composable
DisposableEffect(key) Setup + cleanup on leave (listeners, subscriptions)
SideEffect Publish Compose state to non-Compose code after every successful recomposition
rememberUpdatedState Capture latest callback in long-lived effect without restarting
kotlin
LaunchedEffect(snackbarMessage) {
    snackbarHostState.showSnackbar(snackbarMessage)
}

DisposableEffect(lifecycleOwner) {
    val observer = LifecycleEventObserver { _, event -> /* ... */ }
    lifecycleOwner.lifecycle.addObserver(observer)
    onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}

Do not use LaunchedEffect merely to move ordinary screen initialization into composition when the ViewModel/data layer can own it directly.

A strong answer is:

LaunchedEffect for coroutine work keyed to composition; DisposableEffect when I must unregister on dispose; rememberUpdatedState to avoid stale lambdas in effects.

How do you migrate from Views to Compose incrementally?

What interviewers are testing: Whether you migrate Views to Compose incrementally with shared data layers—not a big-bang rewrite.

Incremental migration beats big-bang rewrite.

Strategy When
New screens in Compose Low-risk features first
ComposeView in Fragment/Activity Embed one composable in XML host
AndroidView in Compose Wrap legacy custom View
Shared ViewModel + repository One business layer for both UIs

Senior point: Measure crash and ANR rates per release; feature-flag Compose screens; keep navigation consistent.

A strong answer is:

I migrate screen by screen with shared ViewModels and data layers. Interop bridges let legacy and Compose coexist until the back stack is ready to simplify.


Coroutines and Flow

What is structured concurrency—and why do seniors need it?

What interviewers are testing: Whether child work has bounded lifetime, cancellation propagation, and predictable failure behavior. Structured concurrency keeps related child coroutines inside an explicit scope so their lifetime, cancellation, and failure are tied to that scope—not orphaned work after the user leaves the screen.

kotlin
viewModelScope.launch {
    val user = async { repo.loadUser() }
    val feed = async { repo.loadFeed() }
    _state.value = UiState.Ready(user.await(), feed.await())
} // cancelled automatically when ViewModel clears

Anti-pattern: GlobalScope.launch for UI work—survives the screen, leaks, hard to test. Contrast with detached/unstructured work that outlives its owner.

A strong answer is:

Structured concurrency ties async work to a lifecycle scope. I use viewModelScope or lifecycleScope—not GlobalScope—for UI-driven coroutines.

Explain coroutine Dispatchers—Main, IO, Default.

What interviewers are testing: Whether you distinguish blocking I/O, CPU work, and main-safe suspend APIs.

Dispatcher Use for
Dispatchers.Main UI updates, fast work on main thread
Dispatchers.IO Blocking I/O APIs — disk, blocking database drivers, legacy blocking network code
Dispatchers.Default CPU-heavy work—parsing, sorting, crypto

Dispatchers.IO is for blocking I/O APIs. Many modern Android clients already expose main-safe suspend functions — for example Retrofit suspend calls and Room suspend APIs — so callers should not blindly wrap every network/database call in withContext(IO) when the API is already main-safe.

kotlin
suspend fun loadFeed(): List<Post> = withContext(Dispatchers.IO) {
    blockingFileApi.readPosts()
}

Senior nuance: Suspend does not automatically mean background — only dispatcher choice or a main-safe library implementation moves work off Main.

A strong answer is:

Main for UI, IO for known blocking I/O, Default for CPU work. I don't wrap every suspend API in IO—the data-layer API should be main-safe, and I move only genuinely blocking work.

launch vs async—when do you use each?

What interviewers are testing: Whether you understand parallel results, failure propagation, and supervisorScope trade-offs.

Builder Returns Use when
launch Job — fire-and-forget side effect Send analytics, trigger sync
async Deferred<T> — await a result Parallel requests you need to combine
kotlin
coroutineScope {
    val profile = async { repo.profile() }
    val settings = async { repo.settings() }
    ProfileScreen(profile.await(), settings.await())
}

Exception handling: async stores a result in Deferred, and await() returns it or rethrows its failure. In a normal coroutineScope, failure of a child also cancels the scope and siblings; use supervisorScope when sibling failures should be isolated.

A strong answer is:

launch for side effects; async when I need parallel results and will await Deferred. I handle failures at await and consider supervisorScope for partial success.

StateFlow vs SharedFlow vs Channel?

What interviewers are testing: Whether you model durable UI state vs transient events with appropriate delivery semantics.

Type Replay Typical use
StateFlow Always has current value ViewModel UI state
SharedFlow Configurable replay Event streams with explicit replay policy
Channel Queue Producer/consumer pipelines

Current Android architecture guidance: StateFlow is the default for ViewModel UI state. For durable outcomes that must not be lost when the UI is absent, model them as state. For genuinely transient UI events, SharedFlow/Channel or Navigation-specific event mechanisms may still be appropriate if their delivery semantics match the requirement.

A strong answer is:

StateFlow for UI state. I model durable outcomes as state; for transient events I use SharedFlow or navigation event APIs when delivery semantics match the requirement—not a default Channel bus from every ViewModel.

Which Flow operators do you use most in production?

What interviewers are testing: Whether you understand cancellation/switching semantics, especially for search and rapidly changing input. Common operators seniors should explain, not only name:

Operator Purpose
map / filter Transform streams
flatMapLatest Switch search query—cancel stale requests
distinctUntilChanged Skip duplicate UI emissions
catch Handle upstream errors
flowOn(Dispatchers.IO) Run upstream on IO
kotlin
searchQuery
    .debounce(300)
    .distinctUntilChanged()
    .flatMapLatest { query ->
        repo.search(query)
    }

Handle errors without converting a failed request into a valid empty result—for example expose a typed success/error state from the repository or ViewModel.

A strong answer is:

debounce plus flatMapLatest for search, distinctUntilChanged for UI—I explain backpressure and cancellation, and handle errors in the repository or ViewModel without masking failures as empty results.

How do you test coroutines and Flow?

What interviewers are testing: Whether you can make async code deterministic through injected dispatchers and virtual time.

Tool Use
runTest Virtual time, controlled dispatchers
TestDispatcher Replace Dispatchers.Main in tests
turbine (library) Assert Flow emissions in order
kotlin
@Test
fun loadSuccess() = runTest {
    val vm = FeedViewModel(FakeRepo())
    vm.load()
    assertEquals(UiState.Success, vm.state.value)
}

Senior point: Prefer keeping blocking-dispatcher decisions in the data layer rather than making ViewModels switch to IO. Inject dispatchers where code actually owns blocking/CPU work and needs deterministic tests.

A strong answer is:

I use runTest with injected dispatchers and fakes for repositories. For Flow I assert emissions with turbine or collect in a controlled scope.


Architecture and modularization

MVVM vs MVI—which do you pick and why?

What interviewers are testing: Whether you choose MVVM or MVI based on screen complexity and team conventions—not pattern dogma.

Pattern State model Best when
MVVM Multiple flows or single UiState Team knows Jetpack; flexible screens
MVI Single immutable state + sealed intents Complex state machines, strict unidirectional flow

MVVM (common Jetpack shape):

kotlin
// ViewModel exposes StateFlow<UiState>
// View calls fun onIntent(action: UserAction)

MVI: UserIntent → reducer → UiState → UI (loop is explicit).

A strong answer is:

I default to MVVM with a single UiState data class for clarity. I choose MVI when the screen is a state machine with many transitions and I want intents logged for debugging.

How do you apply Clean Architecture on Android?

What interviewers are testing: Whether architecture boundaries reduce coupling without adding ceremony for its own sake. Typical layers:

Layer Responsibility
UI Compose, ViewModel, navigation
Domain Use cases, pure Kotlin rules (no Android imports)
Data Repository impl, Room, Retrofit, DataStore

Dependency rule: In a strict Clean Architecture variant, domain-facing abstractions may be defined inward and implemented by data. In pragmatic Android architectures, repository interfaces can live at the boundary that best preserves module ownership and testability.

Pragmatic senior take: Not every app needs a UseCase class per action—avoid ceremony until the team benefits from test seams.

See design patterns for shared pattern vocabulary (repository, factory, observer).

A strong answer is:

UI observes ViewModels; ViewModels call use cases or repositories; data layer implements repos against Room and network. I keep domain Android-free and avoid use-case explosion on small teams.

What belongs in a Repository?

What interviewers are testing: Whether the repository owns data-source coordination and consistency, not merely wraps Retrofit. Repository is the single API for data—UI does not care if data came from cache or network.

Responsibilities:

  • Merge remote + local (Room as source of truth)
  • Expose Flow for reactive UI
  • Handle retry, error mapping, offline
kotlin
class UserRepository @Inject constructor(
    private val api: UserApi,
    private val dao: UserDao
) {
    fun observeUser(id: String): Flow<User?> =
        dao.observeUser(id).map { entity -> entity?.toDomain() }

    suspend fun refresh(id: String) {
        val remote = api.getUser(id)
        dao.upsert(remote.toEntity())
    }
}

A strong answer is:

Repository hides data sources and exposes one observable API. UI collects Flow; refresh writes through to Room so offline still works—the ViewModel decides not-found vs empty UI.

How do you modularize a large Android app?

What interviewers are testing: Whether module boundaries improve ownership/build isolation/API boundaries without producing dependency spaghetti. Modularization improves build time, enforces boundaries, and enables feature teams.

Module type Contains
:app DI graph, navigation host, minimal glue
:feature:* Screen UI + ViewModel
:core:ui Design system, shared composables
:core:data Network, DB, repositories
:core:domain Use cases, models

Rules: Prefer clear, acyclic feature dependencies. Where features need to collaborate, depend on narrow contracts/navigation APIs rather than reaching into another feature's internals.

A strong answer is:

I split by feature and core layers, keep dependencies pointing inward, and avoid feature-to-feature imports. Navigation contracts decouple teams.

Hilt basics—what problem does DI solve on Android?

What interviewers are testing: Whether you understand lifetime and dependency ownership, not annotation memorization. Dependency injection supplies dependencies from the outside—testable, swappable, lifecycle-aware.

Hilt component Lifetime / typical use
SingletonComponent Application lifetime
ActivityRetainedComponent Retained across Activity configuration changes
ViewModelComponent One ViewModel lifetime; dependencies scoped with @ViewModelScoped
kotlin
@HiltViewModel
class FeedViewModel @Inject constructor(
    private val repo: FeedRepository
) : ViewModel()

Interview follow-up: Manual DI works for small apps; Hilt/Koin scale when graphs grow. Prefer constructor injection over field injection.

A strong answer is:

DI makes dependencies explicit and testable. I use Hilt for ViewModel and singleton bindings—fake repos in unit tests without Robolectric.


Networking, persistence, and offline-first

Why is Room often the single source of truth?

What interviewers are testing: Whether UI reads from a durable authoritative model while refresh/sync updates it. Many offline-first designs make a durable local data source such as Room the canonical read source, while network/sync updates it.

Pattern Behavior
UI observes Flow from Room
Network success Upsert into Room
Network failure UI still shows last cached data + error affordance

Benefits: Survives process death, rotation, and airplane mode without custom cache glue.

A strong answer is:

Room gives durable observable state. Network is an update mechanism, not the primary read path—users still see data when offline.

How would you design offline sync with conflict resolution?

What interviewers are testing: Whether you design durable pending writes, idempotency, retries, and conflict semantics. Senior system-design favorite. Outline:

  1. Local write — optimistic UI, queue mutation in outbox table
  2. Background sync — WorkManager worker drains outbox when online
  3. Conflict policy — last-write-wins, server version, or merge per field
  4. Idempotency — client-generated IDs or request keys
Component Role
Room Entities + outbox + sync metadata (updatedAt, syncState)
WorkManager Durable deferred sync with constraints and retry/backoff
API Server version/revision, ETags/conditional writes, or domain-specific conflict metadata

Clarify with interviewer: Chat vs catalog vs financial data need different conflict rules.

A strong answer is:

I persist locally first, sync with WorkManager, and define conflict rules per entity type. I mention idempotency and what the user sees when sync fails mid-flight.

How do you handle API errors end-to-end?

What interviewers are testing: Whether errors are translated into domain/UI decisions without leaking transport implementation details.

Map layers cleanly—do not leak HTTP codes into Compose.

Layer Responsibility
Retrofit Typed responses, interceptors
Repository Map to Result or domain errors
ViewModel UiState.Error with user message + retry
UI Snackbar, inline error, pull-to-refresh
kotlin
sealed class NetworkResult<out T> {
    data class Success<T>(val data: T) : NetworkResult<T>()
    data class HttpError(val code: Int, val body: String?) : NetworkResult<Nothing>()
    data object Offline : NetworkResult<Nothing>()
}

A strong answer is:

I map transport errors to domain results in the repository and expose sealed UI state. Users get actionable messages and retry—not raw 502 text.

How do you implement pagination in a feed?

What interviewers are testing: Whether you understand paging keys, invalidation, refresh/append failure, and local/remote coordination.

Approach When
Paging 3 library Standard RecyclerView/Compose lists—keys, placeholders, retry
Cursor / keyset API Large feeds, stable ordering
Offset Simple APIs; can become expensive or inconsistent under large/changing datasets

Compose: PagingData + LazyColumn + collectAsLazyPagingItems().

Senior points: Duplicate keys break diffing; handle refresh vs append; empty and error states in LoadState.

A strong answer is:

I use Paging 3 with keyset APIs when possible. I explain placeholder behavior, retry, and how refresh invalidates the cache without duplicating items.


Performance, security, and quality

How do you diagnose ANRs and UI jank?

What interviewers are testing: Whether you distinguish ANR, jank, and startup bottlenecks before applying fixes.

Symptom Tool / action
ANR Main thread blocked—check Play Console traces, StrictMode
Jank Perfetto and Android Studio system traces/profilers
Compose Layout Inspector recomposition counts

ANR causes: Blocked main thread, locks, binder/I/O, long callbacks.

Jank causes: Expensive render/layout/draw, bitmap work, large lists.

Startup: Baseline Profiles, startup tracing.

A strong answer is:

I profile before guessing—Main thread stacks for ANR, recomposition counts for Compose jank. Fixes are moving blocking work off Main and stabilizing list state.

What causes memory leaks on Android—and how do you prevent them?

What interviewers are testing: Whether you can spot long-lived references retaining short-lived UI objects.

Cause Prevention
Static reference to Activity Avoid; use Application context carefully
Listener not removed Unregister at the matching lifecycle boundary (onDestroyView, onStop, onDestroy, or DisposableEffect depending on ownership)
Long-lived coroutine holding View Structured concurrency, no Activity in coroutine
Mis-scoped singleton Hilt scopes match lifetime

Tool: LeakCanary in debug builds; heap dumps for stubborn cases.

A strong answer is:

Leaks are usually long-lived references to short-lived UI. I match scope to lifecycle, remove listeners, and use LeakCanary in debug.

What security topics do senior Android interviews cover?

What interviewers are testing: Whether you can identify mobile-specific trust boundaries and storage/network/component risks.

Topic Practice
Network TLS, certificate pinning (when justified)
Sensitive keys/credentials Minimize storage; use Android Keystore-backed cryptographic keys where appropriate and store only the minimum sensitive material required
WebView Disable risky JS bridges; validate URLs
Exported components Minimize exported Activities/Services
Secrets Do not embed privileged server secrets in the APK or deliver them to the client through Remote Config. Keep privileged secrets server-side; use short-lived credentials, backend mediation, and attestation where applicable

Encryption-at-rest design depends on the threat model; do not assume a deprecated convenience wrapper makes secrets safe. EncryptedSharedPreferences and EncryptedFile from androidx.security:security-crypto are deprecated.

Proguard/R8: Obfuscation is not encryption—assume reverse engineering.

A strong answer is:

I minimize sensitive client storage, use Keystore where needed, avoid hard-coded secrets, minimize exported surfaces, and treat R8 as obfuscation—not a vault.


Testing and delivery

What is your Android testing strategy?

What interviewers are testing: Whether you place tests where they provide fast confidence with minimal implementation coupling.

Layer Tools What to test
Unit JUnit, MockK, coroutines test ViewModel, use cases, mappers
Integration Room in-memory, fake servers Repository, DAO queries
UI Compose UI tests, Espresso Critical flows, accessibility

Senior bar: Tests prove behavior, not implementation details—avoid asserting private methods.

CI: run fast unit/static checks on every PR; select critical instrumentation tests per PR and parallelize/shard broader device suites according to CI cost and risk.

A strong answer is:

Heavy unit coverage on ViewModels and repositories; fewer UI tests on money paths. I inject fakes and use runTest for coroutines.

What CI/CD steps matter for Android teams?

What interviewers are testing: Whether the pipeline manages quality, signing, artifact promotion, and staged release risk. Typical pipeline:

  1. Lint (Android Lint, detekt, ktlint)
  2. Unit tests
  3. Assemble debug/release
  4. Instrumentation (optional per PR)
  5. Sign & distribute (Firebase App Distribution, Play Internal)

Versioning: keep versionCode monotonically increasing for Play distribution; versionName can follow semantic/product versioning if the team chooses.

See Git interview questions for branch and review practices that pair with mobile CI.

A strong answer is:

Every PR runs lint and unit tests; release builds bump versionCode and go through staged rollout. I tie Git flow to what CI actually gates.


Mobile system design scenarios

Design an offline-first news feed.

What interviewers are testing: Whether you design offline-first feeds with durable reads, sync ordering, retry, and battery-aware refresh. Structure your answer in layers:

Requirements to clarify: Read offline? Personalization? Media attachments? Stale tolerance?

Layer Choice
UI Compose + Paging 3 + pull-to-refresh
State ViewModel + UiState
Data Room pages + RemoteMediator
Network Cursor/keyset pagination; conditional requests/version metadata where supported
Sync WorkManager prefetch + retry
Images Coil/Glide with disk cache

Edge cases: Airplane mode mid-pagination, process death mid-scroll, duplicate pages on retry.

A strong answer is:

Room plus Paging with RemoteMediator, network as refresh path, WorkManager for background sync, and explicit stale-while-revalidate UX.

Design a real-time chat feature on Android.

What interviewers are testing: Whether you balance real-time delivery, offline queues, ordering, and battery limits on Android. Clarify: One-to-one vs group, delivery receipts, offline queue, end-to-end encryption scope.

Concern Approach
Transport WebSocket or SSE for live; REST for history
Local Room messages table, outbox for sends
Ordering Server sequence or hybrid logical clock
Push FCM for wake; sync on open
UI Paging history upward, optimistic send

Battery: Batch heartbeats; use WorkManager for non-urgent sync. Do not rely on a continuously connected socket for background delivery—Android background limits and battery constraints make push + durable resync the safer model.

A strong answer is:

Optimistic UI with outbox, WebSocket when foreground, FCM plus sync on resume, Room as source of truth for history and pending sends.

How do push notifications fit into app architecture?

What interviewers are testing: Whether you treat push as a wake signal and sync authoritative data—not as the source of truth.

Piece Role
FCM Delivery channel
FirebaseMessagingService Receive, validate, route
Deep links Navigation to target screen
Data vs notification messages Background handling rules differ by OS version, priority, payload type, and OEM behavior

Senior points: Notification channels, permission on Android 13+, dedupe, respect user opt-out, do not put secrets in payload.

A strong answer is:

FCM is a signal, not my source of truth. I handle notification permission and deep links, keep payloads non-sensitive, dedupe messages, and sync authoritative data when the app runs rather than assuming every background message gives me unrestricted execution.


Behavioral and leadership

Tell me about a production incident you resolved.

What interviewers are testing: Whether you narrate a production incident with measurable impact and systemic follow-up.

Use STAR with mobile-specific detail:

  • Situation — spike in ANRs or crashes after release
  • Task — restore stability without rolling back entire feature
  • Action — Play Console stack traces, reproduce on low-RAM device, hotfix, staged rollout
  • Result — crash-free rate recovered, postmortem, added test or monitor

Mention Firebase Crashlytics, Play vitals, or internal dashboards if you have them.

A strong answer is:

I describe a measurable incident—ANR or crash rate—with how I triaged stacks, shipped a fix, and added guardrails so the class of bug cannot silently return.

How do you balance feature delivery with tech debt?

What interviewers are testing: Whether you prioritize debt by user and team velocity risk—not architectural purity. Seniors are judged on trade-offs, not purity.

Framework Example
Risk-based Pay debt touching crash or security first
Boy scout rule Small cleanup in every feature PR
Dedicated capacity Explicitly reserve capacity where recurring platform/debt work warrants it
Metrics Build time, crash rate, lead time

A strong answer is:

I prioritize debt that affects users or velocity—crashes, build times, flaky tests—and negotiate visible platform wins alongside features.

What is predictive back and why does it matter in modern Android navigation?

What interviewers are testing: Whether you integrate system Back, predictive gestures, and navigation state restoration consistently. Predictive back lets users preview where the system Back gesture will take them before completing the gesture.

Topic Senior talking point
System Back Must integrate with app back stack correctly
Navigation Compose / Navigation 3 Choose based on app architecture/migration state; integrate correctly with system Back and predictive-back APIs
Predictive back Requires proper back handling and animations
State restoration Preserve navigation state across recreation/process death where needed

Android's current guidance points new Compose apps toward modern single-Activity navigation with correct back-stack behavior rather than ad hoc per-screen back handling.

A strong answer is:

I design navigation so system Back, predictive back gestures, and restored navigation state all behave consistently—especially in single-Activity Compose apps.

Final prep checklist

Technical drills:

  • Explain lifecycle vs process death with Room recovery
  • Whiteboard MVVM data flow from Compose to network
  • Compare StateFlow vs SharedFlow and launch vs async
  • Walk through Compose side effects and state hoisting
  • Sketch offline-first sync with outbox + WorkManager
  • Name Paging 3 + RemoteMediator responsibilities
  • One performance story (jank, ANR, startup)

Cross-skill refresh:

  • Java / JVM fundamentals for OOP and threading depth
  • Design patterns vocabulary
  • Git for CI and review flow
  • Full stack guide if the role includes backend APIs

Behavioral:

  • Three STAR stories—incident, conflict, technical decision
  • Portfolio or Play Store links with 2-minute walkthrough each

Pattern cheat sheet (quick reference)

Pattern Tool / concept
Survive rotation ViewModel, rememberSaveable
Survive process death Room, DataStore, SavedStateHandle
UI state StateFlow, sealed UiState
One-off events Prefer UI state over ViewModel Channel buses
Offline-first Room source of truth + sync worker
List at scale Paging 3, keyset API
DI Hilt constructor injection
Compose performance Stable types, derivedStateOf, keys
Background work WorkManager, not raw Service unless needed
Testing runTest, fake repositories, Turbine

References


Summary

Senior Android interviews connect Kotlin, Compose, coroutines, and data architecture to real device constraints—process death, offline sync, and main-thread discipline show up as scenario questions. Answer aloud and compare your structure to each section. Pair with JVM depth from our Java guides when interviewers cross into concurrency or OOP.

Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)