Android (Kotlin) Interview Questions and Answers: A Prep Guide for Mobile Developers

Android interview questions are the set of questions hiring teams use to test a mobile developer's Kotlin knowledge, their ability to build screens with Jetpack Compose and their grasp of Android component lifecycles. I have run digital projects since 2012, and I ask these questions from the other side of the table when I hire for mobile teams. In this guide I group them by topic and share answers with short code samples.
My goal is not to hand you a list to memorise. Under each question I also explain what the interviewer actually wants to learn. A strong answer explains the reason, not just the definition. You can find related posts in the software category.
What are the most common Android interview questions?
Android interview questions cover four main areas: Kotlin language basics, concurrency with coroutines and Flow, UI with Jetpack Compose, and Android components with app architecture. To prepare, answer a few questions from each area in your own words, write a small piece of code and explain out loud why it works.
Official sources show why Kotlin dominates these rounds. According to the Android Developers Kotlin-first page, Google announced at Google I/O 2019 that Android development would be increasingly Kotlin-first. The same page states that over 70 Google apps use Kotlin. It also says apps containing Kotlin code are 20% less likely to crash. As a result, almost every current job ad tests Kotlin rather than Java.
I suggest a clear order: Kotlin first, then coroutines, then Compose, and architecture last. Candidates with weak language basics also struggle with Compose. Without lambdas, extension functions and immutability, recomposition is very hard to explain.
What stages does an Android technical interview include?
The process varies by company, because each team has its own needs. Still, the flow I see in the field looks similar in most places. The table below sums up what each stage measures and how you can prepare. The durations are a starting range based on field experience, not a guarantee.
| Stage | What it measures | How to prepare | Typical length |
|---|---|---|---|
| Screening call | Communication, experience, expectations | Describe your published app in two minutes | 20-30 min |
| Concept questions | Kotlin and Android basics | Answer the questions in this post out loud | 45-60 min |
| Live coding | Problem solving, readable code | Build small Compose screens against a timer | 45-90 min |
| Take-home task | Architecture, tests, project layout | Keep a clean sample project on GitHub | 1-3 days |
| System design | Offline mode, sync, scale | Sketch and explain an app's data layer | 45-60 min |
For senior roles, companies also add a behavioural round. There they want a concrete story about a production bug and how you fixed it.
What is the difference between val and var in Kotlin?
val declares a read-only reference that you can assign once. var declares a reference you can reassign. However, the interviewer wants one detail in particular. val fixes the reference, not the object's contents. For example, you can still add items to a MutableList held in a val.
val list = mutableListOf(1, 2)
list.add(3) // works
// list = mutableListOf() // compile error
A good answer also mentions that teams use val by default. They also keep var for values that truly change. That way, readers see at a glance which values can move.
How does const val differ from val?
const val holds a primitive or String known at compile time. In addition, you can only declare it at the top level or inside an object or companion object. A normal val can come from a runtime calculation. Therefore you cannot assign a function result to a const val.
How does null safety work in Kotlin?
Kotlin's type system separates nullable and non-nullable types. For example, String cannot hold null, while String? can. The compiler refuses to let you use a nullable value without a check. In practice, this moves most NullPointerException bugs to compile time.
You can sum up the operators that come up most often like this:
- ?. safe call: returns null if the object is null.
- ?: Elvis operator: uses the right-hand value if the left side is null.
- !! asserts non-null and throws if the value is null.
- ?.let runs a block only when a value exists.
In short, explaining why you avoid !! helps you stand out. Mentioning that platform types from Java code can break null safety also signals real experience.
What are data classes, sealed classes and objects used for?
A data class generates equals, hashCode, toString, copy and componentN functions for classes that carry data. In practice, the copy function is especially useful for UI state. It lets you update state without mutation.
A sealed class defines a closed hierarchy of subclasses. For instance, you can model a screen's Loading, Success and Error states with a sealed interface. Then the compiler checks that your when expression handles every case.
sealed interface UiState {
data object Loading : UiState
data class Success(val items: List<String>) : UiState
data class Error(val message: String) : UiState
}
Next, the object keyword creates a singleton. A companion object gives a class static-like members. On the other hand, interviewers often ask how enum and sealed differ. Enum constants have a single instance each, while sealed subclasses can carry different data.
How do you use extension functions and scope functions?
An extension function lets you add a function to a class without changing the class. Under the hood it compiles to a static function. So it cannot reach private members, and it does not dispatch polymorphically. Interviewers ask about this detail a lot.
Scope functions differ on two axes. The first is whether you access the object as it or this. The second is what the function returns. This summary makes it easier:
- let: uses it and returns the lambda result; common for null checks.
- run: uses this and returns the lambda result.
- apply: uses this and returns the object; good for configuration.
- also: uses it and returns the object; good for side effects like logging.
- with: not an extension; it takes the object as a parameter.
That said, the best answer admits that nesting these functions hurts readability.
What is a coroutine and how does it differ from a thread?
A coroutine is a lightweight unit of work that can suspend. When a thread blocks, that operating system thread sits idle. A coroutine instead suspends at a suspension point and frees the thread for other work. Because of this, you can run thousands of coroutines on a few threads.
A dispatcher question always follows. First, Dispatchers.Main handles UI work. Then Dispatchers.IO handles waiting-heavy work such as network and disk. Dispatchers.Default handles CPU-heavy work. For example, sorting a large list belongs on Default, and reading a database belongs on IO.
viewModelScope.launch {
val users = withContext(Dispatchers.IO) {
repository.fetchUsers()
}
_state.value = UiState.Success(users)
}
You should also know launch versus async. launch starts a Job with no result. async returns a Deferred, and you read its result with await. For the official details, see the Kotlin coroutines documentation.
Why do structured concurrency and coroutine scopes matter?
Structured concurrency means every coroutine belongs to a scope. When you cancel the scope, all its child coroutines cancel too. As a result, forgotten network calls and memory leaks do not outlive the screen.
On Android, built-in scopes handle this for you. viewModelScope cancels when the ViewModel clears. lifecycleScope also cancels when the lifecycle owner reaches its end. Therefore defending GlobalScope usually costs you points.
Error handling continues this topic. In a regular Job, one failing child cancels its siblings. A SupervisorJob keeps the failure inside that child. Also, if you mention that CoroutineExceptionHandler only works on root coroutines, you show that you have tried it for real.
What are the differences between Flow, StateFlow and SharedFlow?
Flow is a cold stream. It does nothing until someone collects it, and it restarts for each collector. However, StateFlow and SharedFlow are hot streams. They can emit values whether or not anyone collects. The table below sums up the differences.
| Type | Cold or hot? | Initial value | Typical use |
|---|---|---|---|
| Flow | Cold | None | Database queries, one-off data streams |
| StateFlow | Hot | Required | Screen state (UI state) |
| SharedFlow | Hot | Optional (replay) | One-time events, broadcasts |
| LiveData | Hot, lifecycle-aware | Optional | Older View-based projects |
StateFlow does not emit the same value twice in a row. In other words, it behaves like distinctUntilChanged. So it is a poor fit for a Snackbar event you may need to show twice. In Compose, collectAsStateWithLifecycle stops collection while the app sits in the background.
What is Jetpack Compose and how does it differ from Views?
Jetpack Compose is Android's declarative UI toolkit. Instead of building a view tree in XML and updating it by hand, you write the UI as a function of state. When state changes, Compose redraws the affected part. Google calls this process recomposition.
A concrete contrast works well in interviews. With Views, you find a TextView with findViewById and call setText. With Compose, you only change the state and the framework does the rest. Google's Thinking in Compose page explains this model in detail.
@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name")
}
Compose and Views can also live together. ComposeView puts Compose inside an XML screen, and AndroidView puts a classic View inside Compose. For that reason, "I migrate screen by screen, not all at once" satisfies most interviewers.
How do remember, rememberSaveable and state hoisting work?
remember keeps a value across recompositions. However, it loses the value on configuration changes such as rotation. Instead, rememberSaveable stores the value in a Bundle. So the value survives configuration changes and process death.
@Composable
fun Counter() {
var count by rememberSaveable { mutableStateOf(0) }
Button(onClick = { count++ }) { Text("Clicks: $count") }
}
State hoisting moves state up from a composable to its caller. The composable takes a value and an onChange lambda as parameters. Then the component stays stateless, reusable and testable. Put simply, you apply the single source of truth principle.
A strong candidate adds one more point. Specifically, business state belongs in the ViewModel. Only transient UI state, such as whether a menu is open, stays inside the composable.
Why do recomposition and performance matter in Android interview questions?
Recomposition starts when a State object that a composable reads changes. Compose tries to rerun only the affected composables. It can skip calls whose parameters are stable and unchanged.
The performance tips interviewers ask about most are these:
- Wrap expensive calculations in remember so they do not rerun on every frame.
- Use derivedStateOf for values derived from fast-changing state.
- Give LazyColumn items a key so items match correctly when the list changes.
- Read values like scroll position as late as possible, for example with lambda-based modifiers.
- Use immutable data classes and prefer stable types over mutable lists.
Saying that you never optimise without measuring also earns points. The recomposition counts in Layout Inspector show which composable runs without need. I apply the same discipline on the web in a Lighthouse performance test.
When do you use side effect APIs?
Composable functions should stay free of side effects, because you do not control when or how often they run. Still, sometimes you need to send an analytics event or register a listener. Compose offers controlled APIs for this:
- LaunchedEffect: runs a coroutine that restarts when its key changes, for example to show a Snackbar.
- rememberCoroutineScope: lets you start a coroutine inside an event such as a click.
- DisposableEffect: for work that needs setup and cleanup; onDispose is mandatory.
- SideEffect: updates a non-Compose object after each successful recomposition.
- produceState: turns a non-Compose source into State.
Interviewers often ask what LaunchedEffect(Unit) means. The key never changes, so the effect runs only when the composable first enters the composition.
How do the Activity and Fragment lifecycles work?
The Activity lifecycle consists of onCreate, onStart, onResume, onPause, onStop and onDestroy. After onStart the screen becomes visible. Then, after onResume, the user can interact with it. You can find the official flow in the Android Developers lifecycle guide.
The real differentiator is the configuration change. When the screen rotates, the system destroys and recreates the Activity by default. Therefore you keep screen state in a ViewModel. You keep small, critical state in SavedStateHandle or rememberSaveable.
Fragments have two lifecycles: the Fragment itself and its view. For example, if you collect a Flow with lifecycleScope instead of viewLifecycleOwner.lifecycleScope, you may update a view that no longer exists. Connect process death to this topic as well. The system can kill a background app, and only saved state comes back.
What are the four core Android components?
The four core components of an Android app are Activity, Service, BroadcastReceiver and ContentProvider. You declare each one in the manifest, and the system can start each one on its own. Interviewers expect one sentence on when to use each.
- Activity: the screen the user interacts with; modern apps often use a single Activity with Compose screens.
- Service: work without UI; long work the user notices needs a foreground service with a notification.
- BroadcastReceiver: listens to system or app broadcasts, such as device boot.
- ContentProvider: shares data safely with other apps.
Intents come next. An explicit Intent targets a specific component. On the other hand, an implicit Intent describes an action, and the system finds a suitable app. On Android 12 and later, components with an intent filter must declare android:exported explicitly.
When should you choose WorkManager for background work?
WorkManager is for deferrable work that must run even if the app closes or the device restarts. Photo uploads, daily sync and log delivery fall into this group. You can add constraints such as network access or charging state.
The interview question usually asks you to choose. Would you use a coroutine, WorkManager or a foreground service? A short rule helps:
- If the work only matters while the screen is open, use a coroutine in viewModelScope.
- If the work must persist and can wait, use WorkManager.
- If the work must start now and the user notices it, such as music or navigation, use a foreground service.
Consequently, "I do everything in a Service" shows that you do not know current Android limits. Battery optimisation and Doze mode restrict background work tightly.
How do you set up a ViewModel and MVVM architecture?
ViewModel is a Jetpack component that keeps UI state through configuration changes. It also separates business logic from the screen. In MVVM, the screen only shows state and forwards user events. The ViewModel fetches data through a repository and emits new state.
Google's guide to app architecture recommends layers today: a UI layer, an optional domain layer and a data layer. Data flows in one direction. State goes down and events go up. This is unidirectional data flow (UDF).
class ProfileViewModel(
private val repo: ProfileRepository
) : ViewModel() {
private val _state = MutableStateFlow<UiState>(UiState.Loading)
val state: StateFlow<UiState> = _state.asStateFlow()
}
Interviewers often ask why the MutableStateFlow stays private. You expose a read-only stream, so only the ViewModel can change state. If MVI comes up, explain that MVI funnels all events into one intent stream and holds state in one object.
How do you use Hilt for dependency injection?
Hilt is the recommended dependency injection library for Android, and it sits on top of Dagger. Instead of creating objects yourself, you describe how to create them. Hilt then provides them in the right scope. This makes it easy to pass a fake repository in tests.
A few annotations give you a solid start. Use @HiltAndroidApp on the application class and @AndroidEntryPoint on Activities. Then add @HiltViewModel on ViewModels, and @Module with @InstallIn for modules. Use @Binds for interfaces and @Provides for third-party objects.
Scope questions come up often too. @Singleton creates one instance for the whole app. @ViewModelScoped lives as long as the ViewModel. In other words, making everything a singleton creates memory and testing problems. You can mention alternatives such as Koin for small projects. Then explain that you prefer Hilt for its compile-time checks.
How do you store local data with Room?
Room is a persistence library on top of SQLite that checks queries at compile time. It has three parts: an @Entity for tables, a @Dao for queries and a @Database class. DAO functions can be suspend functions or return a Flow.
A query that returns a Flow emits a new result whenever the table changes. For this reason, the database becomes the single source of truth in an offline-first design. Network data goes into Room first, and the UI only listens to Room.
Migration questions may also appear. When the schema changes, you raise the version number and add a Migration or an auto-migration. fallbackToDestructiveMigration deletes user data, so production use needs care. Honestly, a real story such as "I lost data once and then wrote migration tests" is the most convincing answer.
How do you write tests for an Android app?
Android tests fall into three layers: unit tests, integration tests and UI tests. Unit tests also run fast on the JVM and check ViewModel and repository logic. UI tests run on a device or an emulator.
For coroutine code, you use runTest and a test dispatcher. Delays then pass instantly in virtual time. For Compose screens, createComposeRule lets you find nodes, click them and check text. For Flow tests, libraries like Turbine make it easier to check emitted values in order.
Interviewers usually connect testability to architecture. For example, if a ViewModel creates a Retrofit instance itself, testing becomes hard. If you inject the dependency through the constructor, you can pass a fake repository. The same testing mindset appears on the web side in this mobile-friendly test guide.
How do you find memory leaks and performance problems?
The most common Android memory leak happens when a long-lived object holds a reference to a short-lived Activity or Context. Storing an Activity in a static field is one example. Likewise, registering a listener and never removing it is another.
These are the diagnostic tools I use in the field:
- LeakCanary: catches leaks in debug builds and shows the reference chain.
- Android Studio Profiler: tracks memory, CPU and network use live.
- Baseline Profiles: improve startup and scrolling through ahead-of-time compilation.
- StrictMode: flags disk or network access on the main thread during development.
ANR (Application Not Responding) questions belong here as well. If you block the main thread for too long, the system tells the user the app is not responding. So you move heavy work off the main thread. I covered how speed affects rankings in how site speed affects SEO. In a mobile app, speed shows up directly in user ratings.
Which live coding tasks come up in Android interview questions?
In live coding, interviewers rarely ask for algorithm puzzles. They usually ask for a small but realistic screen. These are the tasks I see most often:
- A screen that loads a list from an API, shows it in a LazyColumn and handles loading and error states.
- A ViewModel that filters results as the user types, with debounce.
- Also, a Compose form that validates fields and enables the submit button only when valid.
- A simple list of favourites saved in Room that also works offline.
Here the interviewer does not expect perfect code. Instead, they watch how you think. First restate the requirement. Next write the state model as a sealed interface, and then build the screen. Talk through what you do. When you get stuck, stating your assumption beats silent waiting every time.
For instance, in the debounce task a chain of debounce, distinctUntilChanged and flatMapLatest shows your coroutine knowledge in one line.
Which mistakes should you avoid while preparing?
Over the years, the mistakes I see in candidates look alike. If you plan around them, the same technical knowledge leaves a much better impression.
- Memorising definitions and going blank when the interviewer asks why.
- Defending outdated approaches such as AsyncTask or GlobalScope.
- Failing to explain the architecture decisions in your own project.
- Staying silent in live coding and asking no questions.
- Never having planned what you would test and how.
Also, do not underestimate your portfolio. Above all, a small app live on the Play Store is stronger proof than a long list of certificates. If your app has a landing page, following mobile-first design principles also supports a professional image.
How do Android interview questions change at senior level?
At senior level, the focus moves from syntax to decisions. The interviewer no longer asks what StateFlow is. Instead they ask how you would standardise state management across a ten-person team. They expect reasoned choices and clear trade-offs.
Common topics at this level include multi-module structure and build time, and the limits of code sharing with Kotlin Multiplatform. Conflict resolution in offline sync, app size, startup time and CI automation for tests and releases also come up. For example, in a modularisation question you can explain how separating feature and core modules affects the build cache.
Communication matters as much as technical skill in a senior role. Explaining tech debt to a product team feels similar to defending a micro frontend decision in a large web architecture. In both cases you need to show the business impact clearly.
How do you build a study plan for Android interview questions?
Android interview questions cover a wide area, so studying without a plan wastes time. The four-week plan below is a starting suggestion based on field experience, not a guarantee. So adjust it to your level.
- Week 1: Kotlin basics, null safety, collections and scope functions. Try one concept a day with short code.
- Week 2: Coroutines, Flow and test dispatchers. Write a small network request example end to end.
- Week 3: Jetpack Compose, state hoisting and side effect APIs. Build a two-screen app.
- Week 4: Architecture, Hilt, Room and testing. Clean up the project, push it to GitHub and rehearse explaining its architecture.
In short, I recommend a mock interview with a friend at the end of each week. Hearing your own answers reveals gaps much faster than study on paper.
If you are planning a landing site, store visibility or a search strategy for an app or product, take a look at my web design service and my SEO consulting page. You can also reach me directly through the contact page.




