23. Too many recompositions
A Compose list item recomposes on every scroll frame even though its data did not change. The item takes a List<Tag> parameter from a regular Kotlin class. What is a likely cause?
- A.Compose always recomposes every item on every frame
- B.LazyColumn does not support keys
- C.The parameter type is considered unstable, so Compose cannot safely skip the item; use immutable collections or stable types and pass keys
- D.Kotlin data classes cannot be used in Compose
Show answer
Answer: C. The parameter type is considered unstable, so Compose cannot safely skip the item; use immutable collections or stable types and pass keys
Compose skips a composable only when it can prove all parameters are stable and equal. The standard List interface may be backed by a mutable list, so classes holding it can be treated as unstable. Using kotlinx immutable collections, marking types @Immutable or @Stable, and enabling strong skipping mode reduces needless recompositions. Use the Layout Inspector to check recomposition counts.
24. Polling loop that ignores cancellation
After the ViewModel is cleared, logs show this loop still logging errors over and over. Why?
viewModelScope.launch {
while (true) {
try {
delay(1000)
poll()
} catch (e: Exception) {
log(e)
}
}
}- A.viewModelScope is never cancelled
- B.delay() does not respond to cancellation inside a try block, so the coroutine keeps sleeping and polling until the system kills the process or the user force-stops the app
- C.A while (true) loop can never be cancelled in Kotlin
- D.catch (e: Exception) also catches the CancellationException thrown by delay(), so the loop swallows cancellation and keeps going; rethrow it or catch only specific exceptions
Show answer
Answer: D. catch (e: Exception) also catches the CancellationException thrown by delay(), so the loop swallows cancellation and keeps going; rethrow it or catch only specific exceptions
Cancellation in coroutines works by throwing CancellationException at suspension points like delay. Catching all exceptions hides it, and every later delay throws again immediately, giving a fast loop that logs forever. Rethrow CancellationException, catch narrower types, or call ensureActive() in the catch block.
25. Property declared in the class body
What does this print?
data class User(val id: Int) {
var name: String = ""
}
val a = User(1).apply { name = "Asha" }
val b = User(1).apply { name = "Ravi" }
println(a == b)- A.false
- B.It does not compile
- C.It throws at runtime
- D.true
Show answer
Answer: D. true
Generated equals(), hashCode(), toString() and copy() use only properties declared in the primary constructor. name lives in the class body, so both objects compare equal on id alone. This surprises people using such objects in sets or diffing.
26. Checking a generic type at runtime
Why does this function need inline and reified?
inline fun <reified T> List<Any>.firstOfType(): T? =
firstOrNull { it is T } as T?- A.Without reified, T is erased and "is T" is not allowed
- B.reified makes the function run on a background thread
- C.inline is required for any generic function
- D.reified lets T be a nullable type only
Show answer
Answer: A. Without reified, T is erased and "is T" is not allowed
On the JVM, generic types are erased at runtime, so a normal function cannot check "is T". An inline function with a reified type parameter is copied into the call site with the real type, making type checks and T::class possible.
27. Why viewModelScope uses Main.immediate
viewModelScope uses Dispatchers.Main.immediate. What does "immediate" change?
- A.It runs the coroutine on a background thread for speed
- B.It makes the coroutine ignore any cancellation request
- C.Already on main, the code runs now instead of being queued
- D.It forces a new frame to draw before the coroutine starts
Show answer
Answer: C. Already on main, the code runs now instead of being queued
Dispatchers.Main posts work to the main looper even when you are already on it, adding a delay. Main.immediate runs the code right away in that case, so state updates from a click are applied in the same frame.
28. Suspending in finally after cancel
After this coroutine is cancelled, the server never gets the "release" call. Why, and what is the fix?
try {
api.lock(id)
doWork()
} finally {
api.release(id) // suspend fun
}- A.finally blocks never run at all inside coroutines
- B.delay() inside a try block disables cancellation
- C.Suspend calls throw once cancelled; use NonCancellable
- D.The release call must be made from GlobalScope
Show answer
Answer: C. Suspend calls throw once cancelled; use NonCancellable
Once cancelled, any suspending function that checks cancellation throws CancellationException at once, so api.release() never runs. withContext(NonCancellable) lets short cleanup code suspend. Keep such cleanup brief.
29. Locking around a suspend call
You need to ensure only one token refresh runs at a time, and the refresh is a suspend call. Why is a synchronized block a poor fit?
- A.You cannot suspend inside synchronized; use Mutex.withLock
- B.synchronized makes the code run on the main thread
- C.synchronized only works with Java classes
- D.synchronized always causes a deadlock on Android
Show answer
Answer: A. You cannot suspend inside synchronized; use Mutex.withLock
The compiler forbids suspension points inside synchronized blocks, and blocking a thread while waiting wastes dispatcher threads. kotlinx.coroutines Mutex suspends waiting coroutines instead, and withLock releases it even on exceptions.
30. Testing a 30-second retry
A ViewModel retries after delay(30_000). How do you test it without waiting 30 real seconds?
- A.Use runBlocking and Thread.sleep(30_000) in the test
- B.Use runTest, where delays run on virtual time
- C.Remove the delay from the production code for tests
- D.Mock the delay function itself with Mockito
Show answer
Answer: B. Use runTest, where delays run on virtual time
runTest runs coroutines on a TestScheduler with virtual time, so delays are skipped. advanceTimeBy or advanceUntilIdle control progress. The ViewModel’s dispatcher must be a test dispatcher (for example Dispatchers.setMain).
31. Sealed classes and exhaustive switch
A `sealed class PaymentState` has three subclasses: Idle, Loading and Failed. A switch expression handles only Idle and Loading. What happens?
- A.Failed values silently fall through and return null
- B.The analyzer reports a compile error because the switch is not exhaustive
- C.It compiles, and a runtime error appears only when Failed occurs
- D.Dart automatically adds a default branch that returns Idle
Show answer
Answer: B. The analyzer reports a compile error because the switch is not exhaustive
A sealed class can only be extended in its own library, so the compiler knows every subtype. Switch expressions over it must be exhaustive, and a missing case is a compile-time error. This catches forgotten UI states the moment a new subclass is added.
32. Missing brackets in Boolean
On many search tools, AND is processed before OR. What does this string actually return?
java OR kotlin AND spring
- A.Only Java or Kotlin people who also have Spring
- B.Anyone with Java, plus Kotlin people who also have Spring
- C.Only people with all three words
- D.Nobody, because the string is invalid
Show answer
Answer: B. Anyone with Java, plus Kotlin people who also have Spring
Without brackets, AND binds first, so it reads as java OR (kotlin AND spring). Every Java profile appears, even without Spring. Adding brackets, (java OR kotlin) AND spring, gives the intended result.