Android · 16 min
Android technical interview questions and answers
Android technical interview questions and answers: lifecycle, process death, coroutines, data, security and testing, with the follow-up to each.
Android · 13 min read
Kotlin interview questions as Android loops ask them: null safety, coroutines, cancellation, Flow, sealed types and generics, with each follow-up.
This page is for Android engineers whose loop includes a Kotlin or coroutines screen. It is the Android Kotlin interview, not the backend one: every question here is asked in the context of a ViewModel, a lifecycle or a database on a phone. You get twelve Kotlin interview questions with the answer that scores, the trap, and the follow-up, plus a worked exchange on cancellation, the topic most coroutine follow-ups come back to.
Nobody is scored on syntax. A Kotlin question in an Android loop is scored on whether you know what the compiler guarantees, what it does not, and who owns the work you just launched. The candidate who says “cancellation is cooperative” before the interviewer asks is already ahead. The one who says “viewModelScope cancels everything” has just given the interviewer the next question.
!!. What does that cost you, and how would you reduce it?”The answer that scores: “Every !! is an unchecked assertion that turns a contract mistake into a crash in production. I would not search and replace it with ?., because that silently swallows states that should fail loudly. First I list every site and work out why each value can be null, fix the producers first, usually unannotated Java or late initialisation, and then ratchet the count down in CI so it cannot grow back.”
The trap: replacing !! with ?. and an empty default everywhere. The crash goes away and a corrupted state continues.
The follow-up: “Null here means the data is corrupted. Your default lets the transaction continue.” Recovery line: “Then continuing is the bug. I would fix the producing boundary and fail explicitly where going on would hide invalid state.”
The answer that scores: “A type alias is only a new name for the same type, so a UserId alias can be passed where an OrderId alias is expected. A value class is a distinct type at compile time and can avoid the allocation when it is unboxed. I use them for ids and validated scalar values. They do get boxed when used as nullable, generic or through an interface, so ‘free’ is not quite right.”
launch calls and combines the results through shared variables. What is wrong?”What is scored: structured concurrency as a tool, not a phrase.
The answer that scores: “launch does not return a value, and the shared variables are a race: the combine step can read one before it is assigned. I would use two async calls inside one coroutineScope and await both. The parent then owns cancellation and failure: if one fails, the other is cancelled, which is right when the screen needs both.”
The trap: protecting the variables with a Mutex. The race on the values is gone; the missing result, sibling failure and parent cancellation are still implicit.
The answer that scores: “From the failure policy. If the result is useless without all of them, coroutineScope, so one failure cancels the rest. If some are optional, supervisorScope, and the optional branch maps its failure to an explicit ‘not available’ instead of pretending the aggregate succeeded.”
The follow-up: “One request authorises the user and another only decorates the screen. Same failure behaviour?” No. Separate them, and model partial success on purpose.
The answer that scores: “Cancellation is cooperative. I keep work in the caller’s job tree, use suspending APIs that check cancellation, add ensureActive() or yield() in long CPU loops, and never swallow CancellationException. A broad catch (e: Exception) that returns a fallback turns cancellation into success. Cleanup that has to suspend runs in withContext(NonCancellable), kept as small as possible.”
The follow-up: “Your catch consumed the cancellation. What does the parent think happened?” That the child completed normally, while its work and the UI state continue out of step.
GlobalScope, or a custom scope that outlives the screen?”The answer that scores: “Cancellation, error handling and retained references become invisible. Nobody cancels the work on logout, nobody sees its failure, and a captured activity callback leaks. When the application really owns the work, I inject a named application scope with a SupervisorJob and a documented shutdown. When the work must finish even if the process dies, it belongs in WorkManager, not in any scope.”
The answer that scores: “StateFlow drops a new value that is equal to the current one. Change the existing object in place, assign that same reference back, and no emission happens. The fix is immutable snapshots with correct equality. Adding distinctUntilChanged to a StateFlow does nothing, because it already conflates by equality.”
The follow-up: “A second refresh returns the same data but must stop the loading indicator.” Then the loading transition belongs in the state, so the two states are not equal.
The answer that scores: “A cold flow starts its work for each collector. A hot flow, like StateFlow or SharedFlow, runs independently of collectors. On Android the question is who keeps the upstream alive. A database or location flow shared with stateIn and SharingStarted.WhileSubscribed stops when the UI stops collecting, which saves battery. A cold flow collected by three composables runs three times.”
The answer that scores: “Sealed interface when all the parent does is fix the list of variants, because a subtype might need another superclass or sit in two hierarchies. Sealed class when the parent carries common state or constructor rules. Both give exhaustive when checks. Choosing a sealed class by habit uses up the only superclass slot for nothing.”
The answer that scores: “Mark T by which way values flow: out T if the type only returns them, in T if it only accepts them, and nothing at all for a repository that both reads and writes. Reified types only exist on inline functions, so an interface method cannot be reified. I keep a class-token method in the interface, which Java can call, and add the reified version as an inline extension for Kotlin callers.”
The trap: trying to declare a reified interface method, then fixing the compile error with unchecked casts at every call site.
The answer that scores: “inline copies the function and its lambda into the call site, which removes the lambda allocation and allows reified types and non-local returns. noinline keeps one lambda as an object. crossinline keeps it inline but forbids a non-local return, because the lambda runs later, from another context. I use inline for reified APIs or a measured hot path. A large public inline function copied into hundreds of call sites costs code size, and on Android that shows up in the dex.”
var ‘for flexibility’. Your answer?”The answer that scores: “val by default, so references are not reassigned and mutation stays behind one owner. But val protects the reference, not the object: two callers holding the same val MutableList can both change it. So I confine the mutable container, expose a read-only type or an immutable copy, and use var for private state with a clear owner.”
This is the exchange, condensed. It shows how a Kotlin round moves from recall to reasoning in three pushes.
Interviewer: “A ViewModel launches a coroutine to build a report. The user leaves the screen, and CPU usage continues for several seconds. Why?”
Strong answer: “Clearing the ViewModel cancels viewModelScope, and that cancellation only works if the code cooperates. A report generator stuck in a tight CPU loop, or one that calls blocking code, swallows CancellationException, or hands the work to another scope, keeps running after the job is cancelled. I would look at the job tree and the blocking boundary first.”
suspend fun buildReport(rows: List<Row>): Report = withContext(Dispatchers.Default) {
val lines = ArrayList<Line>(rows.size)
for ((index, row) in rows.withIndex()) {
if (index % 500 == 0) ensureActive() // lets cancellation stop the loop
lines += render(row)
}
Report(lines)
}Push: “It is already inside withContext(Dispatchers.IO). So why is it still running?” “Moving a blocking call to IO protects the main thread. Cancellation still cannot reach inside the call: the coroutine is marked cancelled, and the thread sits blocked until the call finishes. Main-safety and responsiveness to cancellation are two different guarantees.”
Push: “Would you call Thread.interrupt()?” “Only when the blocking API says in its docs that interrupting it is safe. For a blocking library I cannot change, runInterruptible turns coroutine cancellation into a thread interrupt, so I do not call interrupt() by hand. Otherwise I would use the library’s own cancellation, bound the call with a timeout where that makes sense, and make a late result harmless.”
Push, the one that decides it: “The user leaves while the report is being written to disk.” Now the question is which lifetime owns the write. If a half-written file is worse than no file, the write goes to a temporary file and is renamed at the end. If the report must be saved even after the user leaves, the screen was the wrong owner, and the work belongs in WorkManager.
The hire signal in that exchange is the distinction between main-safety and cancellation, and the refusal to promise that a dispatcher changes what a blocking call does.
Answer each question aloud before reading the answer that scores. Then take the follow-up and change one constraint yourself: a shorter lifetime, a failure after success, a second caller. If the answer does not change when the constraint does, you have memorised it rather than understood it. To pick a book for the whole loop, see Android interview books. The Android platform questions that sit on top of this are on Android technical interview questions.

Android · 16 min
Android technical interview questions and answers: lifecycle, process death, coroutines, data, security and testing, with the follow-up to each.

Android · 14 min
Compose interview questions on recomposition, state, side effects and performance, with the weak answer, the answer that scores and the follow-up.

Android · 16 min
Twelve senior Android interview questions with what each scores, a weak and a senior answer, and the follow-up: coroutines, Flow, Compose, process death.