Senior Android Developer Interview Questions and Answers

Senior Android developer interview questions for 2026: Kotlin, Jetpack Compose, coroutines, MVVM/MVI, offline-first design, performance, and system design with sample answers.

Published

Tech reviewed byDeepak Prasad

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 45 questions with sample answers aligned with what candidates report in 2026 loops: Kotlin depth, Jetpack Compose, coroutines and Flow, architecture, DI, offline-first design, performance, testing, and mobile 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.

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?

Loops vary by company size, but a common pattern looks like this:

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?

A senior prep plan should produce spoken narratives and one reference architecture, not only flashcards.

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
6 Mocks + STAR stories + Git CI basics Two timed mocks with verbal walkthrough

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
OOP, interfaces data class, sealed hierarchies Java OOP interviews
Concurrency Coroutines, Flow Threads, executors in Java part 2
Patterns Delegation, extension functions Design patterns in Java

Interview tip: When asked about threading, connect coroutines as compiler-transformed state machines to why they scale better than raw threads for I/O.


Kotlin language depth

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

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?

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?

inline copies bytecode at the call site—removing lambda allocation overhead and enabling reified generics at runtime.

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 everything 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?

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?

Interviewers care less about reciting every callback and more about state survival and resource cleanup.

Callback Senior talking point
onCreate One-time setup; restore from savedInstanceState
onStart / onStop Visible but may not be interactive
onResume / onPause Foreground interaction; pause heavy work
onDestroy Final cleanup; may be skipped on configuration change

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

Process death: OS may kill your process under memory pressure. Disk (Room, DataStore, SavedStateHandle) survives; plain Activity fields do not.

A strong answer is:

I focus on what survives rotation vs process death. ViewModel and persisted storage survive configuration changes; only persisted state survives process death.

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

ViewModel survives configuration changes and exposes UI state to the screen.

Belongs in ViewModel:

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

Does not belong:

  • Context, View, or Activity references (leaks)
  • Navigation side effects without a clear pattern (prefer one-off events channel)
  • 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?

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, SavedStateHandle (small UI state)
No In-memory singletons, static caches, uncleared ViewModel if not restored

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

Test: Enable "Don't keep activities" and "Background process limit" in developer options; walk through your critical flows.

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?

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?

Compose runs in three phases for each frame:

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

Performance angle: Skipping recomposition is cheaper than laying out or drawing again. Stability and smart state reads reduce composition work.

A strong answer is:

Composition decides what UI should exist; layout positions it; draw renders it. Senior performance work targets skipping unnecessary composition first.

What triggers recomposition—and how do you reduce it?

Recomposition runs when state read during composition changes.

Reduction tactics:

Technique Purpose
Stable / immutable models Compiler can skip unchanged composables
remember Cache expensive objects across recompositions
derivedStateOf Recompute only when dependencies change
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.

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?
API Survives
remember Recomposition and configuration change (same process)
rememberSaveable Also process death via SavedStateRegistry (Bundle-sized data)

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

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

A strong answer is:

remember is in-process only. rememberSaveable survives process death for small UI state; large data belongs in Room or DataStore.

LaunchedEffect vs DisposableEffect vs SideEffect?

Compose side-effect APIs tie work to the composition lifecycle:

API Use when
LaunchedEffect(key) Suspend work tied to keys—load data, collect Flow
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(userId) {
    viewModel.loadUser(userId)
}

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

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?

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?

Structured concurrency means every coroutine has a parent scope—when the scope cancels, children cancel too. No 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.

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.
Dispatcher Use for
Dispatchers.Main UI updates, fast work on main thread
Dispatchers.IO Blocking I/O—network, disk, database
Dispatchers.Default CPU-heavy work—parsing, sorting, crypto
kotlin
suspend fun loadFeed(): List<Post> = withContext(Dispatchers.IO) {
    api.fetchPosts()
}

Senior nuance: Suspend does not mean background—only withContext or dispatcher choice moves work off Main. Room and Retrofit provide main-safe suspend APIs when used correctly.

A strong answer is:

Main for UI, IO for blocking I/O, Default for CPU. I use withContext explicitly when library code might block.

launch vs async—when do you use each?
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 exceptions surface at await()—use supervisorScope when one failure should not cancel siblings.

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?
Type Replay Typical use
StateFlow Always has current value UI state
SharedFlow Configurable replay One-off events, bus (use carefully)
Channel Queue, not hot state Producer/consumer pipelines
kotlin
private val _events = Channel<UiEvent>(Channel.BUFFERED)
val events = _events.receiveAsFlow()

UI events pattern: Prefer Channel or SharedFlow with replay=0 for navigation/snackbar—avoid re-firing on rotation.

A strong answer is:

StateFlow for state; SharedFlow or Channel for events that should not replay. I never put short-lived toast events in StateFlow without consuming them.

Which Flow operators do you use most in production?

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 { q -> repo.search(q) }
    .catch { emit(emptyList()) }

A strong answer is:

debounce plus flatMapLatest for search, distinctUntilChanged for UI, catch for graceful degradation—I explain backpressure and cancellation, not just operator names.

How do you test coroutines and Flow?
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: Inject dispatchers—do not hardcode Dispatchers.IO inside ViewModels if you want fast unit 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?
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?

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: Inner layers never depend on outer layers. Domain defines repository interfaces; data module implements them.

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?

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 { it ?: User.empty(id) }

    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.

How do you modularize a large Android app?

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: Feature modules do not depend on each other directly—navigate via interfaces or deep links. Share only through core APIs.

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?

Dependency injection supplies dependencies from the outside—testable, swappable, lifecycle-aware.

Hilt component Scope
SingletonComponent App-wide (OkHttp, Room)
ActivityRetainedComponent ViewModel
ViewModelComponent ViewModel-specific deps
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?

Offline-first apps read from disk first and refresh in the background.

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?

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 Guaranteed retry, battery-aware
API Version vectors or ETags

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?

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?
Approach When
Paging 3 library Standard RecyclerView/Compose lists—keys, placeholders, retry
Cursor / keyset API Large feeds, stable ordering
Offset Simple admin lists only—poor at scale

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?
Symptom Tool / action
ANR Main thread blocked—check Play Console traces, StrictMode
Jank Systrace, Perfetto, Android Studio Profiler
Compose Layout Inspector recomposition counts

Common causes: Disk/network on Main, over-recomposition, bitmap work on UI thread, lock contention.

Fix pattern: Move work to Dispatchers.Default/IO, use Baseline Profiles for startup, lazy list keys, image loading library with sizing.

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?
Cause Prevention
Static reference to Activity Avoid; use Application context carefully
Listener not removed DisposableEffect, onDestroy cleanup
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?
Topic Practice
Network TLS, certificate pinning (when justified)
Storage EncryptedSharedPreferences / EncryptedFile for secrets
WebView Disable risky JS bridges; validate URLs
Exported components Minimize exported Activities/Services
Secrets No API keys in APK—remote config or attestation patterns

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

A strong answer is:

I encrypt sensitive local data, avoid hard-coded secrets, minimize exported surfaces, and treat R8 as obfuscation—not a vault.


Testing and delivery

What is your Android testing strategy?
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 unit tests on every PR; shard instrumentation tests nightly.

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?

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: semantic versionCode monotonic for Play Store.

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.

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 API, ETag per page
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.

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.

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?
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

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

A strong answer is:

FCM delivers; the app routes through a single handler to navigation and analytics. I design for permission denial and data-only messages in background.


Behavioral and leadership

Tell me about a production incident you resolved.

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?

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 10–20% sprint for platform work
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.

Final-week checklist for senior Android interviews?

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:

Behavioral:

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

A strong answer is:

In the final week I rehearse offline-first design aloud, drill Compose and coroutines trade-offs, and polish three STAR stories tied to shipped apps—not slides of API trivia.


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 Channel, SharedFlow(replay=0)
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

Official Android documentation

On-site prep


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 …