Skip to content

Senior Android Engineer Interview Questions & Answers

40 questions for Senior Android Engineer interviews — 21 medium, 19 hard.

About the Senior Android Engineer role

A senior Android engineer designs architecture for large features, makes technology choices, reviews code and mentors juniors. They own app quality metrics such as crash-free rate, ANRs and startup time, plan modularisation and lead migrations like XML to Compose. Interviews focus on system design for mobile, offline sync, performance and trade-off discussions, along with leadership and ownership examples. Clear communication is equally important.

Mobile system design Modularisation Performance tuning Mentoring Release management
Solved for this roleStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
3 questions free Unlock the rest in seconds
01

How do you model API results and errors cleanly for the UI?

Save to ▾
Show answer
Map transport-level outcomes into a sealed result type in the data layer, for example Success(data), HttpError(code, message) and NetworkError. Catch IOException for connectivity, HttpException for non-2xx codes, and serialization exceptions for bad payloads, and never catch CancellationException. The ViewModel converts these into user-friendly UiState messages. Keep DTOs separate from domain models so API changes do not ripple through the UI.
02

A payment SDK integration fails only in release builds. What would you check?

Save to ▾
Show answer
First check R8 or ProGuard: release builds shrink and obfuscate code, and the SDK may need keep rules for classes used by reflection or JSON parsing. Check the SDK documentation for required rules and add them to proguard-rules.pro. Also verify the release signing key SHA is registered with the provider, the correct production keys and base URLs are used, and network security config allows the domains.
03

How do you migrate from SharedPreferences to DataStore without losing user data?

Save to ▾
Show answer
Use SharedPreferencesMigration when creating the DataStore: preferencesDataStore(name = "settings", produceMigrations = { ctx -> listOf(SharedPreferencesMigration(ctx, "old_prefs")) }). On first access, DataStore copies the values and deletes the old file. Update all reads and writes to go through the new repository at once, because mixing both stores leads to inconsistent values. Test the migration with existing preference files.
04

Your app has 60 Gradle modules and builds take 15 minutes. How would you speed them up?

Save to ▾
Show answer
Profile with Gradle build scans to find slow tasks. Enable the configuration cache and build cache, including a remote cache in CI. Use KSP instead of kapt, avoid unnecessary api dependencies so changes do not recompile many modules, use convention plugins, and keep non-transitive R classes. Check heavy annotation processors and give Gradle enough memory. Developers can build only the module they work on with a sample app.
05

How do you mentor junior Android developers?

Save to ▾
Show answer
I pair on their first tasks, explain the why behind patterns rather than only giving fixes, and review pull requests with specific, kind comments plus links to docs. I give them small features end to end so they see the full flow from API to UI, and I ask them to present one learning in our weekly tech sync. I check in regularly so they feel safe asking questions early.
06

Compare kotlinx.serialization, Moshi and Gson for JSON parsing.

Save to ▾
Show answer
kotlinx.serialization is Kotlin-first, uses a compiler plugin with no reflection, respects default values and nullability, and works in Kotlin Multiplatform. Moshi supports Kotlin through codegen and is also reliable. Gson uses reflection, ignores Kotlin nullability and default values, and can put null into non-null fields, causing crashes later; it is in maintenance mode. For new Android projects, kotlinx.serialization is the common choice.
07

Describe a time you improved app performance with measurable results.

Save to ▾
Show answer
Situation: our home screen had many frozen frames on budget phones. Task: improve scroll smoothness. Action: I captured a system trace, found image decoding on the main thread and unstable list parameters causing recomposition, fixed image sizing with Coil, added keys and immutable models, and created a Baseline Profile. Result: frozen frames dropped by about 60 percent and startup improved by 25 percent in Macrobenchmark.
08

How do you structure a large Android app into modules?

Save to ▾
Show answer
Common practice is feature modules (feature:cart, feature:profile), core modules (core:network, core:database, core:designsystem, core:model) and an app module that wires them together. Feature modules depend on core modules but not on each other; navigation is connected through the app module or route contracts. Benefits are faster incremental builds, clear ownership and enforced boundaries. Gradle convention plugins keep module build files consistent.
09

The API team wants to switch to GraphQL. What changes on the Android side?

Save to ▾
Show answer
You would use a client like Apollo Kotlin, which generates type-safe models from the schema and queries at build time, and supports normalised caching and coroutines. Benefits are fetching exactly the fields a screen needs and fewer round trips. Costs include learning the tooling, more complex caching and error handling since errors can come with partial data. I would pilot it on one feature before migrating everything.
10

Explain the network security configuration file.

Save to ▾
Show answer
network_security_config.xml, referenced from the manifest, controls TLS behaviour without code. Since Android 9, cleartext HTTP is blocked by default; you can allow it for specific debug domains. You can trust user-added CAs only in debug builds for tools like Charles or proxies, and define certificate pins with expiry. Keeping cleartext disabled in production is an important security baseline.
11

Users on slow 2G and 3G networks in small towns report the app keeps failing. How do you improve it?

Save to ▾
Show answer
Set reasonable connect and read timeouts, retry idempotent requests with exponential backoff, and cache responses so screens show something offline. Reduce payload size with gzip, pagination and fields filtering, and load images in smaller sizes with placeholders. Show clear offline and retry states instead of generic errors. Test with the emulator network throttling and real devices on poor connections, and track failure rates by network type.
12

Write a use case that combines cart items and a coupon into a final total.

Save to ▾
Show answer
class GetCartTotalUseCase @Inject constructor( private val cartRepo: CartRepository, private val couponRepo: CouponRepository ) { operator fun invoke(): Flow<Double> = combine(cartRepo.items, couponRepo.activeCoupon) { items, coupon -> val subtotal = items.sumOf { it.price * it.qty } val discount = coupon?.let { minOf(subtotal * it.percent / 100, it.maxOff) } ?: 0.0 subtotal - discount } }
13

Two screens show different values for the same cart count. How do you fix the architecture?

Save to ▾
Show answer
Each screen probably fetches and caches its own copy. Create a single CartRepository, scoped as a singleton, that owns cart data in Room or an in-memory StateFlow, and have both ViewModels observe its Flow instead of keeping private copies. Mutations go through the repository so every observer updates automatically. This enforces the single source of truth and removes the need for manual refresh calls between screens.
14

A WorkManager job is not running on some phones from certain manufacturers. How do you investigate?

Save to ▾
Show answer
Some manufacturers apply aggressive battery optimisations that delay or kill background work. Check the job state with WorkManager's getWorkInfosForUniqueWorkFlow and the background task inspector in Android Studio. Verify constraints are not too strict. Guide users to allow unrestricted battery for the app only where the feature truly needs it, use expedited work for urgent tasks, and use FCM to trigger sync when timely delivery matters.
15

How does certificate pinning work and what are its risks?

Save to ▾
Show answer
Certificate pinning makes the app trust only specific public key hashes for your domain, using OkHttp CertificatePinner or the Network Security Config pin-set. It protects against attackers with a rogue or user-installed CA intercepting traffic. The risk is that if the server certificate changes and the pin does not match, all users lose connectivity until they update. Always pin a backup key and set an expiration for config-based pins.
16

How would you lead an architecture decision when senior developers disagree?

Save to ▾
Show answer
I would write a short decision document stating the problem, two or three options with trade-offs, and constraints such as team skill and timeline, then build a small spike of the top options on a real screen. We review with measurable criteria like test effort and build time, decide, and record it as an architecture decision record. Once decided, everyone commits, and we revisit only if new facts appear.
17

What is the difference between @Provides and @Binds in Hilt?

Save to ▾
Show answer
@Provides is used in a module function that constructs and returns an object, needed for third-party classes like Retrofit or Room that you cannot annotate with @Inject. @Binds is used on an abstract function in an abstract module to tell Hilt which implementation to use for an interface, when the implementation already has an @Inject constructor. @Binds generates less code and is slightly more efficient.
18

How would you implement real-time order tracking updates in an app?

Save to ▾
Show answer
Options are polling, WebSockets, Server-Sent Events, or push notifications. For a live tracking screen, a WebSocket via OkHttp exposed as a callbackFlow works well, started when the screen is visible and closed when it leaves. Add reconnection with backoff, heartbeat pings, and fall back to polling on failure. When the app is in background, rely on FCM pushes for status changes instead of keeping the socket open.
19

How do Room migrations work?

Save to ▾
Show answer
When you change the schema, increase the database version and provide a Migration(from, to) with SQL like ALTER TABLE, or use AutoMigration for simple changes with exported schemas. Without a migration, Room throws at startup unless you call fallbackToDestructiveMigration, which wipes user data. Export schemas to version control and test migrations with MigrationTestHelper so upgrades from every old version are verified.
20

You are starting a new app for a fintech startup. Which architecture and libraries would you choose and why?

Save to ▾
Show answer
I would use Kotlin, Jetpack Compose with Material 3, MVVM with unidirectional data flow, Hilt for DI, Coroutines and Flow, Retrofit with OkHttp and kotlinx.serialization, Room for offline data, DataStore for preferences, WorkManager for background sync, and Navigation Compose. For fintech, add certificate pinning considerations, encrypted storage via Keystore, Play Integrity checks, and strong test coverage. Start modular from day one with core and feature modules.
21

Write a Hilt module that provides Retrofit and a repository binding.

Save to ▾
Show answer
@Module @InstallIn(SingletonComponent::class) object NetworkModule { @Provides @Singleton fun provideRetrofit(): Retrofit = Retrofit.Builder() .baseUrl("https://api.example.com/") .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) .build() @Provides @Singleton fun provideApi(retrofit: Retrofit): ShopApi = retrofit.create(ShopApi::class.java) } @Module @InstallIn(SingletonComponent::class) abstract class RepoModule { @Binds abstract fun bindRepo(impl: ProductRepositoryImpl): ProductRepository }
22

Explain how you would use Room with Kotlin Multiplatform.

Save to ▾
Show answer
Since Room 2.7, Room supports Kotlin Multiplatform for Android, iOS and desktop. You define entities, DAOs and the database in commonMain, use the bundled SQLite driver, and create the database builder in platform-specific source sets with expect and actual. DAO functions must be suspend or return Flow in common code. This lets teams share the data layer while keeping native UIs, which some Indian product companies are adopting.
23

Write a periodic sync job with WorkManager that runs only on unmetered network.

Save to ▾
Show answer
class SyncWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) { override suspend fun doWork(): Result = try { repo.sync() Result.success() } catch (e: IOException) { if (runAttemptCount < 3) Result.retry() else Result.failure() } } val request = PeriodicWorkRequestBuilder<SyncWorker>(6, TimeUnit.HOURS) .setConstraints(Constraints(requiredNetworkType = NetworkType.UNMETERED)) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS) .build() WorkManager.getInstance(context).enqueueUniquePeriodicWork( "sync", ExistingPeriodicWorkPolicy.KEEP, request)
24

Users report battery drain after the latest update. How do you investigate?

Save to ▾
Show answer
Check Android vitals for excessive wake locks and wakeups, then use Battery Historian or the Energy Profiler to look at wake locks, alarms, location requests, jobs and network activity. Common causes are frequent location updates, a foreground service left running, polling instead of push, and WorkManager jobs scheduled too often. Fix by batching work, using lower location priority, stopping services promptly and adding proper constraints.
25

How do you build an adaptive layout for phones, foldables and tablets in Compose?

Save to ▾
Show answer
Use WindowSizeClass from the Material 3 adaptive library to choose layouts by width class, not device type. On compact width show a bottom navigation bar; on medium use a navigation rail; on expanded use a list-detail layout with ListDetailPaneScaffold or NavigationSuiteScaffold. Avoid locking orientation, since Android 16 ignores orientation restrictions on large screens. Test with resizable emulators and foldable postures.
26

Tell me about a time a bug you introduced reached production. How did you handle it?

Save to ▾
Show answer
Situation: a migration I wrote missed a default value, and some users lost their saved addresses. Task: fix it and regain trust. Action: I told my lead immediately, we paused the rollout, I wrote a correct migration with a test covering upgrades from the last three versions, and we restored data from the server for affected users. Result: impact stayed small, and migration tests became mandatory in our CI.
27

How do you design an Android client to support API versioning and forced updates?

Save to ▾
Show answer
Send the app version in a header on every request, have the backend support older versions for a defined period, and use versioned endpoints for breaking changes. Add a remote config value for minimum supported version and use the Play In-App Updates API with immediate flow for critical updates and flexible flow otherwise. Show a clear message rather than failing silently when the app is too old.
28

The backend team changed a field from a number to a string and the app crashed for all users. How do you prevent this?

Save to ▾
Show answer
Short term, make parsing lenient for that field, for example with a custom serializer, and handle parse failures without crashing. Long term, agree on API contracts with versioning, use contract tests or a shared OpenAPI schema checked in CI, add ignoreUnknownKeys and default values in kotlinx.serialization, and use remote config or a minimum supported app version mechanism. Monitor parse errors as non-fatal events.
29

How would you roll out a risky new feature to millions of users safely?

Save to ▾
Show answer
Ship it behind a remote feature flag defaulted off, release the build with a staged rollout and monitor crash-free and ANR rates. Then enable the flag for internal users, then 1, 5, 25 and 100 percent, watching business metrics and errors at each step. Keep a kill switch ready, define rollback criteria beforehand, and keep the old path until the new one is stable.
30

How do you write tests for a Room DAO?

Save to ▾
Show answer
Create an in-memory database with Room.inMemoryDatabaseBuilder in an instrumented test or with Robolectric, get the DAO, insert test data and assert query results. For Flow queries, collect the first value with first() or use Turbine. Close the database in @After. Test edge cases like conflict strategies and ordering. In-memory databases are fast and isolated, so each test starts clean.
31

Product wants a feature flag to turn a new checkout on or off without releasing an update. How do you design it?

Save to ▾
Show answer
Use a remote config service with defaults bundled in the app, wrapped in a FeatureFlags interface injected via Hilt. Fetch and cache values at startup with a sensible timeout, and read the flag at navigation decision points rather than deep inside UI code. Keep both checkout paths tested, log which variant users see for analytics, and plan to remove the old path and the flag after rollout.
32

How do you handle one-time navigation after a successful login in MVVM?

Save to ▾
Show answer
Recommended practice is to reflect the result in state, such as isLoggedIn = true in UiState, and have the UI react with LaunchedEffect(state.isLoggedIn) to navigate, then the ViewModel marks it consumed if needed. An alternative is a Channel exposed as Flow and collected with lifecycle awareness. Avoid calling navigation directly from the ViewModel, because it should not reference NavController or views.
33

The app's cold start takes 4 seconds. How do you improve it?

Save to ▾
Show answer
Measure first with Macrobenchmark StartupTimingMetric on a release build. Then reduce work in Application.onCreate by deferring SDK initialisation, using App Startup or lazy injection, and moving disk I/O off the main thread. Simplify the first screen, avoid blocking on network before showing UI, use the SplashScreen API properly, and add a Baseline Profile, which often improves startup by about 20 to 30 percent.
34

How do you refresh an expired access token automatically?

Save to ▾
Show answer
Use an OkHttp Authenticator, which is called when the server returns 401. Inside it, synchronise so only one refresh runs at a time, call the refresh endpoint with the refresh token, save the new tokens and return the original request with the new header. If the request already carried the latest token and still failed, or refresh fails, return null and log the user out. Limit retries to avoid loops.
35

What is the difference between MVVM and MVI?

Save to ▾
Show answer
Both separate UI from logic. MVI (Model-View-Intent) is stricter: the UI sends intents or actions, a reducer produces a single new immutable state, and side effects are handled separately. It gives one state object per screen and an easy-to-replay history. MVVM is more flexible, sometimes exposing several streams. In practice many Compose apps use MVVM with a single UiState data class, which is close to MVI.
36

What are Baseline Profiles?

Save to ▾
Show answer
A Baseline Profile is a list of classes and methods used in critical journeys like startup and scrolling, shipped with the app. The Android Runtime uses it to compile that code ahead of time on install, avoiding slow interpretation and JIT on first runs. You generate it with the Baseline Profile Gradle plugin and Macrobenchmark tests. Compose and many Jetpack libraries ship their own profiles, but an app-specific one gives larger gains.
37

Write an OkHttp interceptor that adds a bearer token to every request.

Save to ▾
Show answer
class AuthInterceptor @Inject constructor( private val tokenStore: TokenStore ) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val token = tokenStore.currentAccessToken() val request = chain.request().newBuilder().apply { if (token != null) header("Authorization", "Bearer $token") }.build() return chain.proceed(request) } } val client = OkHttpClient.Builder() .addInterceptor(authInterceptor) .build()
38

Your banking app must pass a security audit. What would you check on the Android side?

Save to ▾
Show answer
Check secure storage with Keystore and no secrets in code, TLS only with pinning where justified, no sensitive data in logs, FLAG_SECURE on sensitive screens, root and tamper checks using Play Integrity, exported components reviewed, WebView with JavaScript bridges restricted, R8 obfuscation, session timeout, and biometric authentication with BiometricPrompt and CryptoObject. Use the OWASP MASVS as the checklist and fix findings by severity.
39

Write a Room database class with a migration that adds a column.

Save to ▾
Show answer
@Database(entities = [NoteEntity::class], version = 2, exportSchema = true) abstract class AppDatabase : RoomDatabase() { abstract fun noteDao(): NoteDao } val MIGRATION_1_2 = object : Migration(1, 2) { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL("ALTER TABLE notes ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0") } } val db = Room.databaseBuilder(context, AppDatabase::class.java, "app.db") .addMigrations(MIGRATION_1_2) .build()
40

How do you measure app quality after release?

Save to ▾
Show answer
Track crash-free users and ANR rate in Play vitals and Crashlytics, keeping them below Play's bad behaviour thresholds of 1.09 percent user-perceived crash rate and 0.47 percent ANR rate. Monitor startup time, slow and frozen frames, network error rates and key funnel metrics like login success. Read Play reviews and ratings by version. Set alerts and review these numbers in every release meeting.

Frequently asked questions

What does a Senior Android Engineer do?

A senior Android engineer designs architecture for large features, makes technology choices, reviews code and mentors juniors. They own app quality metrics such as crash-free rate, ANRs and startup time, plan modularisation and lead migrations like XML to Compose. Interviews focus on system design for mobile, offline sync, performance and trade-off discussions, along with leadership and ownership examples. Clear communication is equally important.

Which skills are tested in a Senior Android Engineer interview?

Expect questions on Mobile system design, Modularisation, Performance tuning, Mentoring, Release management. Most rounds also ask you to explain a project and solve one practical problem live.

How do I prepare for a Senior Android Engineer interview?

Revise App Architecture, MVVM and Hilt, Testing, Performance and Play Store, Room, DataStore and WorkManager, Networking and APIs first using the topic sheets, practise the questions on this page aloud, then do one full mock interview to practise timing.