Skip to content

Android UI Engineer (Jetpack Compose) Interview Questions & Answers

40 questions for Android UI Engineer (Jetpack Compose) interviews — 3 easy, 33 medium, 4 hard.

About the Android UI Engineer (Jetpack Compose) role

An Android UI engineer focuses on building polished, accessible and fast interfaces with Jetpack Compose and Material 3. They create reusable design system components, animations and adaptive layouts for phones, tablets and foldables, and work closely with designers. Performance profiling of recomposition and scrolling, screenshot testing and accessibility compliance are key responsibilities in consumer apps with large user bases. Compose testing is part of the job.

Compose and Material 3 Design systems Animations Accessibility UI performance profiling
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 build a single-activity app with the Navigation component?

Save to ▾
Show answer
Keep one Activity hosting a NavHost, and represent each screen as a Compose destination or Fragment. Define routes in a navigation graph, preferably with type-safe routes using Kotlin serialization in Navigation 2.8 and later. Use nested graphs for flows like login or checkout, pop the back stack with popUpTo when finishing a flow, and handle deep links on destinations. This simplifies shared transitions and state handling.
02

What is a ContentProvider and when would you build one?

Save to ▾
Show answer
A ContentProvider exposes data through a URI-based interface with query, insert, update and delete, and handles permissions for cross-app access. You need one when sharing data with other apps, providing search suggestions, or supporting widgets that read your data. FileProvider is a ready-made provider for sharing files securely with content URIs instead of file paths. For data used only inside your app, Room is simpler.
03

How do you find and fix memory leaks in an Android app?

Save to ▾
Show answer
Add LeakCanary to debug builds; it detects retained Activities, Fragments and ViewModels and shows the reference chain. Common leaks are static or singleton references to Context or Views, listeners not unregistered, inner classes holding Activity references, and Fragment view bindings not cleared in onDestroyView. Fix by using application context, lifecycle-aware components and weak references only where needed. Confirm with Memory Profiler heap dumps.
04

Write a Compose UI test that checks an error appears when login is clicked with an empty email.

Save to ▾
Show answer
@get:Rule val rule = createComposeRule() @Test fun showsErrorForEmptyEmail() { rule.setContent { var state by remember { mutableStateOf(LoginState()) } LoginScreen( state = state, onLogin = { state = state.copy(error = "Email required") } ) } rule.onNodeWithText("Login").performClick() rule.onNodeWithText("Email required").assertIsDisplayed() }
05

How do you make Compose UI accessible?

Save to ▾
Show answer
Give icons and images a meaningful contentDescription, or null when decorative. Keep touch targets at least 48dp, which Material components handle by default. Use semantics modifiers to merge descendants for list items, mark headings with heading(), and describe custom actions. Support font scaling by using sp and avoiding fixed heights for text. Check colour contrast and test with TalkBack and the Accessibility Scanner.
06

What is a repository and why is it the single source of truth?

Save to ▾
Show answer
A repository hides where data comes from and gives the rest of the app one API, such as getProducts(): Flow<List<Product>>. With an offline-first design, the repository reads from Room and refreshes from the network in the background, writing results to the database. The UI always observes the database, so it is the single source of truth. This avoids inconsistent copies of data across screens.
07

Explain the Fragment lifecycle and why viewLifecycleOwner matters.

Save to ▾
Show answer
A Fragment has its own lifecycle (onCreate to onDestroy) and a separate view lifecycle (onCreateView to onDestroyView). When a fragment goes to the back stack its view is destroyed but the fragment instance may stay. If you observe LiveData or collect flows with the fragment itself as owner, observers pile up and can touch a dead view. Use viewLifecycleOwner and clear view binding references in onDestroyView.
08

How does coroutine cancellation work, and why must code be cooperative?

Save to ▾
Show answer
Cancelling a Job sets it to cancelling state, and the next suspension point such as delay, withContext or a suspending library call throws CancellationException. A tight CPU loop with no suspension keeps running, so check isActive or call ensureActive() or yield() inside it. Do not swallow CancellationException in a generic catch block; rethrow it. Use finally or withContext(NonCancellable) for cleanup that must finish.
09

Tell me about a time you handled a production crash under pressure.

Save to ▾
Show answer
Situation: after a release, crash-free users dropped from 99.6 to 97 percent. Task: stop the impact fast. Action: I checked Crashlytics, found a crash on Android 14 caused by a missing foreground service type, paused the staged rollout in Play Console, shipped a hotfix with a test on an emulator for that API level, and wrote a short postmortem. Result: crash rate recovered within a day and we added API-level checks to our release checklist.
10

A composable makes an API call every time the screen recomposes. What is wrong and how do you fix it?

Save to ▾
Show answer
The call is placed directly in the composable body, which runs on every recomposition. Composables must be side-effect free. Move loading to the ViewModel, triggered in init or by a user event, and expose the result as StateFlow. If something must start from composition, wrap it in LaunchedEffect with a stable key like the item ID, so it runs once per key rather than on every recomposition.
11

Explain the steps to publish an app on the Play Store.

Save to ▾
Show answer
Create a developer account, then an app in Play Console. Complete the store listing, content rating, data safety form, target audience, privacy policy and permissions declarations. Build a signed release AAB with a higher versionCode, upload it to internal testing first, then closed or open testing, and finally production with a staged rollout. New personal accounts must run a closed test with at least 12 testers for 14 days before production.
12

What are launch modes and task affinity?

Save to ▾
Show answer
Launch modes control how an Activity instance joins a task. standard creates a new instance each time. singleTop reuses the instance at the top and calls onNewIntent. singleTask keeps one instance in its task and clears activities above it. singleInstance puts it alone in a task, and singleInstancePerTask exists since Android 12. Intent flags like FLAG_ACTIVITY_CLEAR_TOP give similar control. Most modern single-activity apps rarely need them.
13

Explain StateFlow versus SharedFlow.

Save to ▾
Show answer
StateFlow is a hot flow that always has a current value, replays only the latest value to new collectors and skips emissions equal to the current value. It is ideal for UI state. SharedFlow is a configurable hot flow with no required initial value, optional replay and buffer, and it emits every value including duplicates. It suits one-off events like navigation or snackbars, though many teams prefer a Channel or state-based events for those.
14

Users report the app loses form data when they switch to another app and come back after a while. Why?

Save to ▾
Show answer
The system killed the process to reclaim memory while the app was in the background. When the user returns, Android recreates the Activity but ViewModel data is gone because the process died. Fix it by storing small UI state in SavedStateHandle or rememberSaveable, which survive process death, and persisting drafts to Room or DataStore. Test with Don't keep activities or adb shell am kill.
15

Explain LaunchedEffect, DisposableEffect and SideEffect.

Save to ▾
Show answer
LaunchedEffect(key) launches a coroutine when it enters composition and restarts when the key changes, used for one-time loads or showing a snackbar. DisposableEffect(key) runs setup and requires an onDispose block for cleanup, used for registering listeners or lifecycle observers. SideEffect runs after every successful recomposition to publish Compose state to non-Compose code, such as analytics. Choose keys carefully so effects do not restart unexpectedly.
16

The Play Store rejected your update for a policy violation about permissions. What do you do?

Save to ▾
Show answer
Read the rejection email and policy centre message carefully to see which permission or declaration failed, often SMS, call log, background location, all-files access or exact alarms. Remove the permission if a less sensitive alternative exists, such as the SMS Retriever API or photo picker. Otherwise update the permission declaration form with a clear use case and video. Check the merged manifest for permissions added by libraries.
17

What is a BroadcastReceiver and what limits apply since Android 8?

Save to ▾
Show answer
A BroadcastReceiver responds to broadcast intents. Since Android 8, most implicit broadcasts cannot be received by receivers declared in the manifest; you register them at runtime with registerReceiver while the app is running. A few exemptions exist, like BOOT_COMPLETED and locale change. Since Android 14, runtime receivers must specify RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED. onReceive runs on the main thread, so keep it short or use goAsync.
18

What is CompositionLocal and when should you use it?

Save to ▾
Show answer
CompositionLocal passes data implicitly down the tree without threading it through every parameter, like LocalContext, LocalDensity or MaterialTheme values. Create one with compositionLocalOf when values change often, or staticCompositionLocalOf when they rarely change. Use it for cross-cutting values like theme or analytics loggers, not for regular screen data, because implicit dependencies make composables harder to understand and test.
19

Tell me about a time you disagreed with a designer about a UI decision.

Save to ▾
Show answer
Situation: a designer wanted a custom tiny close icon on a full-screen dialog. Task: keep the app accessible without blocking the design. Action: I showed that the touch target was below the 48dp guideline and that TalkBack had no label, and prototyped the same look with a larger invisible touch area. Result: we kept the visual style, passed accessibility checks, and agreed to review touch targets in future design handoffs.
20

How do LazyColumn keys improve performance and correctness?

Save to ▾
Show answer
By default, items are identified by position, so inserting or deleting an item can make Compose reuse state for the wrong item and recompose more than needed. Supplying key = { it.id } gives each item a stable identity, so remembered state, animations with animateItem and scroll position follow the right item. Also supply contentType for mixed lists so compositions are reused efficiently. Keys must be unique and Bundle-saveable.
21

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.
22

Write code to show a notification with a channel.

Save to ▾
Show answer
fun showOrderNotification(context: Context, text: String) { val channelId = "orders" if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( channelId, "Order updates", NotificationManager.IMPORTANCE_DEFAULT) context.getSystemService(NotificationManager::class.java) .createNotificationChannel(channel) } val n = NotificationCompat.Builder(context, channelId) .setSmallIcon(R.drawable.ic_order) .setContentTitle("Order update") .setContentText(text) .setAutoCancel(true) .build() NotificationManagerCompat.from(context).notify(1001, n) }
23

Write a Gradle configuration for release signing and minification using environment variables.

Save to ▾
Show answer
android { signingConfigs { create("release") { storeFile = file(System.getenv("KEYSTORE_PATH") ?: "release.jks") storePassword = System.getenv("KEYSTORE_PASSWORD") keyAlias = System.getenv("KEY_ALIAS") keyPassword = System.getenv("KEY_PASSWORD") } } buildTypes { release { isMinifyEnabled = true isShrinkResources = true proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") signingConfig = signingConfigs.getByName("release") } } }
24

How do you test a Compose UI?

Save to ▾
Show answer
Use the Compose testing APIs with createComposeRule or createAndroidComposeRule. Find nodes by text, content description or testTag, then perform actions and assertions, like onNodeWithText("Login").performClick() and onNodeWithTag("error").assertIsDisplayed(). Test stateless composables with fake state for speed. Tests synchronise with Compose automatically. Screenshot testing tools like Roborazzi or Paparazzi catch visual regressions without a device.
25

What does derivedStateOf do and when should you use it?

Save to ▾
Show answer
derivedStateOf creates state computed from other state, and only triggers recomposition when the computed result changes. A classic example is showing a scroll-to-top button: val showButton by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } }. Without it, every scroll pixel would recompose. Do not use it when the result changes as often as the input, because it only adds overhead.
26

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.
27

How do you set up CI/CD for an Android app?

Save to ▾
Show answer
On every pull request, run lint, ktlint or detekt, unit tests and a debug build in a CI service like GitHub Actions or Bitrise, with Gradle caching. On merge to main, build a signed release AAB using secrets, run instrumented tests on emulators or a device farm, and upload to the Play internal track with Gradle Play Publisher or fastlane. Promote to production manually after QA with a staged rollout.
28

Describe a project where you improved code quality for the whole team.

Save to ▾
Show answer
Situation: our app had no shared conventions and reviews took days. Task: speed up reviews and reduce bugs. Action: I proposed ktlint and detekt in CI, created a module template with a convention plugin, and wrote a one-page guide on ViewModel and repository patterns with examples. Result: review time dropped by about half over two months, and new joiners shipped their first feature faster.
29

The APK size grew to 120 MB and installs from small towns are dropping. How do you reduce it?

Save to ▾
Show answer
Analyse the build with APK Analyzer. Publish as an App Bundle so users get only their density, language and ABI. Enable R8 and resource shrinking, convert images to WebP or vectors, remove unused libraries and duplicate dependencies, and move large optional features or assets to Play Feature Delivery or Play Asset Delivery. Avoid bundling all native libraries for every ABI. Track size in CI.
30

What is the difference between a started service, bound service and foreground service?

Save to ▾
Show answer
A started service runs after startService until it stops itself. A bound service offers a client-server interface to components that bind to it and stops when all unbind. A foreground service shows a persistent notification and has higher priority, used for music, navigation or ongoing calls. Since Android 14 a foreground service must declare a type such as mediaPlayback or location. For deferrable background work, use WorkManager instead.
31

How does Compose interoperate with existing XML views?

Save to ▾
Show answer
To use Compose inside views, add a ComposeView to an XML layout or Fragment and call setContent, setting a ViewCompositionStrategy suited to the lifecycle. To use a view inside Compose, wrap it with AndroidView(factory = { MapView(it) }, update = { }). This allows migrating screen by screen, starting with new screens or leaf components. Share the theme using a Compose theme adapter or by mirroring tokens.
32

What is the difference between a deep link and an Android App Link?

Save to ▾
Show answer
A deep link is any intent filter that opens your app for a URI; if several apps can handle it, the user sees a chooser. An App Link is an HTTPS deep link verified with android:autoVerify and a Digital Asset Links file at /.well-known/assetlinks.json on your domain, containing your app's signing certificate fingerprint. Verified links open your app directly. Remember to include the Play App Signing key fingerprint.
33

What does R8 do in a release build?

Save to ▾
Show answer
R8 shrinks code by removing unused classes and members, optimises bytecode by inlining and removing dead branches, obfuscates names to make reverse engineering harder and reduce size, and shrinks resources when enabled. It needs keep rules for code accessed by reflection, such as some serialization or SDK classes. Upload the mapping file to Play Console and Crashlytics so obfuscated stack traces can be decoded.
34

How do you implement theming with Material 3 in Compose?

Save to ▾
Show answer
Wrap the app in a MaterialTheme with a ColorScheme, Typography and Shapes. Use lightColorScheme and darkColorScheme, pick based on isSystemInDarkTheme(), and use dynamicLightColorScheme or dynamicDarkColorScheme on Android 12 and above for Material You colours. Components read MaterialTheme.colorScheme.primary and similar roles, so avoid hardcoding colours. Extend the theme with a custom CompositionLocal for brand-specific tokens.
35

Write a unit test for a ViewModel using a fake repository and runTest.

Save to ▾
Show answer
class ProductViewModelTest { @get:Rule val mainRule = MainDispatcherRule() @Test fun loadsProductsIntoState() = runTest { val fake = FakeProductRepository(listOf(Product("1", "Pen", 10.0))) val vm = ProductViewModel(fake) advanceUntilIdle() val state = vm.state.value assertFalse(state.loading) assertEquals(1, state.items.size) assertNull(state.error) } }
36

How do you pass data between screens and what is the size limit?

Save to ▾
Show answer
Traditionally through Intent extras or Fragment arguments as a Bundle with primitives, Parcelable or Serializable objects. Bundles go through Binder transactions, which have a shared limit of about 1 MB, so large data causes TransactionTooLargeException. Pass an ID instead and load the data from a repository or database on the next screen. With Navigation, use type-safe route arguments and a shared ViewModel only when screens truly share state.
37

A text field loses its typed value after rotation. How do you fix it?

Save to ▾
Show answer
The value is probably held with remember, which is lost when the Activity is recreated. Either use rememberSaveable for local UI state, or keep the text in the ViewModel and pass it down with onValueChange. For data that must survive process death too, store it in SavedStateHandle in the ViewModel. Avoid storing text field state asynchronously, because delayed updates can cause cursor jumps.
38

How does Android handle background execution limits and Doze mode?

Save to ▾
Show answer
Doze defers network access, jobs, alarms and syncs when the device is idle, with short maintenance windows. App Standby Buckets limit how often apps run based on usage. Since Android 8, background services are restricted, and since Android 12 apps cannot start foreground services from background except in specific cases. Use WorkManager for deferrable work, FCM high-priority messages for urgent pushes, and exact alarms only when truly required.
39

Network calls keep running after the user leaves a screen and results arrive late. How do you fix this?

Save to ▾
Show answer
The work is likely launched in GlobalScope, a custom scope never cancelled, or an Application-level scope. Move it to viewModelScope so it is cancelled when the ViewModel is cleared, and make sure the calls are cancellable, which Retrofit suspend functions are. If work must outlive the screen, like uploading a file, move it to WorkManager or an application scope deliberately, and do not update UI from it.
40

QA says a bug happens only on one manufacturer's phones. How do you debug it?

Save to ▾
Show answer
Get exact device model, OS and skin versions, and steps. Use a remote device lab or Firebase Test Lab to reproduce if you lack the device. Collect logcat and bug reports, and compare behaviour around known differences like battery optimisation, autostart restrictions, custom permission dialogs or WebView versions. Add targeted logging or a non-fatal Crashlytics event, then apply a fix guarded by checks only if necessary.

Frequently asked questions

What does a Android UI Engineer (Jetpack Compose) do?

An Android UI engineer focuses on building polished, accessible and fast interfaces with Jetpack Compose and Material 3. They create reusable design system components, animations and adaptive layouts for phones, tablets and foldables, and work closely with designers. Performance profiling of recomposition and scrolling, screenshot testing and accessibility compliance are key responsibilities in consumer apps with large user bases. Compose testing is part of the job.

Which skills are tested in a Android UI Engineer (Jetpack Compose) interview?

Expect questions on Compose and Material 3, Design systems, Animations, Accessibility, UI performance profiling. Most rounds also ask you to explain a project and solve one practical problem live.

How do I prepare for a Android UI Engineer (Jetpack Compose) interview?

Revise Jetpack Compose, Testing, Performance and Play Store, Android Components and Lifecycle first using the topic sheets, practise the questions on this page aloud, then do one full mock interview to practise timing.