Skip to content
RED SAGSkills

Kotlin interview questions and answers

Kotlin questions test the language features Android teams use every day, especially null safety and coroutines.

Below are 32 Kotlin questions with the correct answer and a short explanation, grouped from fresher to experienced level. Try each one before you open the answer.

What Kotlin interviews usually check

  • ✓Null safety
  • ✓Data classes and sealed classes
  • ✓Coroutines, scopes and suspend functions
  • ✓Flows
  • ✓Extension functions and collections

Kotlin interview questions for freshers easy

1. Kotlin Elvis operator

What does this Kotlin code print when user is null?

val user: User? = null
val name = user?.name ?: "Guest"
println(name)
  1. A.null
  2. B.It throws a NullPointerException
  3. C.An empty string
  4. D.Guest
Show answer

Answer: D. Guest

The safe call user?.name returns null when user is null instead of throwing. The Elvis operator ?: then returns the right-hand value when the left side is null, so the result is "Guest".

2. Kotlin val with a mutable list

What does this Kotlin code print, and would the commented-out line compile if you removed the '//'?

val items = mutableListOf(1, 2)
items.add(3)
// items = mutableListOf(4)
println(items)
  1. A.It prints [1, 2, 3]; the commented line would not compile because items is a val
  2. B.It does not compile, because add() cannot be called on a val
  3. C.It prints [1, 2], because add() returns a new list
  4. D.It prints [1, 2, 3]; the commented line would compile because the list is mutable
Show answer

Answer: A. It prints [1, 2, 3]; the commented line would not compile because items is a val

val means the reference cannot be reassigned, not that the object is immutable. A MutableList held in a val can still be changed with add(), so it prints [1, 2, 3]. Reassigning items to a new list is a compile error because the variable itself is read-only.

3. launch vs async in coroutines

In Kotlin coroutines, what is the main difference between launch and async?

  1. A.launch always runs on a background thread, async always runs on the main thread
  2. B.async blocks the calling thread until its work finishes, while launch suspends it and lets the UI keep drawing frames
  3. C.launch returns a Job for work with no result; async returns a Deferred whose result you get with await()
  4. D.They are identical, and async is deprecated
Show answer

Answer: C. launch returns a Job for work with no result; async returns a Deferred whose result you get with await()

Both start a coroutine in a scope, and the dispatcher decides the thread. launch is for work where you do not need a value back and returns a Job you can cancel or join. async returns a Deferred, and await() suspends until its value is ready, which is handy for running several requests in parallel.

4. lateinit or by lazy

Which statement about Kotlin’s lateinit and by lazy is correct?

  1. A.lateinit works only on Int; lazy works only on String
  2. B.lateinit: a var set later; lazy: a val built on first use
  3. C.Both are allowed only inside companion objects
  4. D.lazy is for a var, while lateinit is for a val
Show answer

Answer: B. lateinit: a var set later; lazy: a val built on first use

lateinit var says "I will assign this non-null value before use", and reading it early throws UninitializedPropertyAccessException. It cannot be used with primitive types. by lazy runs its initializer once, on first access, and only works with val.

5. Where to put a factory function

You want MyFragment.newInstance(id) callable without an instance, like a static method in Java. Where should it go?

  1. A.In a private nested data class
  2. B.In an init block of MyFragment
  3. C.In a lateinit property
  4. D.In a companion object inside MyFragment
Show answer

Answer: D. In a companion object inside MyFragment

Members of a companion object are accessed through the class name. It is the usual place for factory functions and constants tied to a class. Add @JvmStatic if Java callers need a real static method.

6. What suspend means

What does marking a function with suspend do?

  1. A.It runs the function on a new background thread
  2. B.It makes the function run on the main thread only
  3. C.It delays the function call by one UI frame
  4. D.It can pause without blocking; it adds no thread
Show answer

Answer: D. It can pause without blocking; it adds no thread

suspend means the function can pause at suspension points and resume later. Where it runs depends on the coroutine’s dispatcher. Blocking code inside a suspend function still blocks unless moved with withContext.

7. Sorting 2 lakh items

You need to sort and filter 2 lakh records in memory, which is CPU-heavy. Which dispatcher fits?

  1. A.Dispatchers.Main
  2. B.Dispatchers.Default
  3. C.Dispatchers.Unconfined
  4. D.A new Thread per item
Show answer

Answer: B. Dispatchers.Default

Dispatchers.Default is sized to the number of CPU cores and is meant for CPU-bound work. Dispatchers.IO allows many more threads for blocking calls such as file or network I/O. Main is for UI work.

8. Reading a file with withContext

What does this function do?

suspend fun loadConfig(file: File): String =
    withContext(Dispatchers.IO) {
        file.readText()
    }
  1. A.Runs readText on an I/O thread and returns its result
  2. B.Starts a fire-and-forget job and returns Unit
  3. C.Blocks the main thread until reading finishes
  4. D.Launches a new coroutine in GlobalScope
Show answer

Answer: A. Runs readText on an I/O thread and returns its result

withContext switches to the given dispatcher, runs the block, and returns its value to the caller, which resumes on its original dispatcher. This makes the function main-safe to call from UI code.

9. delay versus Thread.sleep

Inside a coroutine on the main dispatcher, why use delay(500) rather than Thread.sleep(500)?

  1. A.sleep is more accurate, so it is always preferred
  2. B.delay runs the rest of the code on a new thread
  3. C.They behave exactly the same inside coroutines
  4. D.delay suspends; sleep blocks the thread and UI
Show answer

Answer: D. delay suspends; sleep blocks the thread and UI

Thread.sleep blocks the thread, so on the main thread the UI cannot draw or respond. delay suspends the coroutine and frees the thread for other work, and it is also cancellable.

10. Waiting two seconds in a coroutine

Inside a Unity coroutine, how do you pause for two seconds of game time without freezing the game?

  1. A.Thread.Sleep(2000);
  2. B.while (Time.time < 2f) { }
  3. C.yield return new WaitForSeconds(2f);
  4. D.return 2f;
Show answer

Answer: C. yield return new WaitForSeconds(2f);

Yielding WaitForSeconds tells Unity to resume the coroutine after two seconds of scaled game time, while every other system keeps running. Thread.Sleep or a busy loop block the main thread and freeze the whole game. Use WaitForSecondsRealtime if the wait should ignore Time.timeScale.

11. Why use parentheses

Why put brackets around (Java OR Kotlin) in the string 'Android AND (Java OR Kotlin)'?

  1. A.So OR is applied first, before AND
  2. B.So the words are searched as one phrase
  3. C.So the search ignores these two words
  4. D.So results are sorted by those words
Show answer

Answer: A. So OR is applied first, before AND

Brackets tell the engine which part to work out first. Here it finds profiles with Java or Kotlin, then keeps only those that also mention Android. Without brackets, engines may combine terms in an order you did not intend.

Kotlin intermediate interview questions medium

12. Exposing UI state from a ViewModel

An Android ViewModel exposes screen state (loading, content or error) to a Compose screen. Which type is the usual choice, and why?

  1. A.A SharedFlow with replay 0, so a screen that starts collecting after rotation waits for the next change before it draws anything
  2. B.A Channel, so each state value is consumed exactly once
  3. C.A LiveData created inside a coroutine on every call
  4. D.A StateFlow, because it always holds the latest value and gives it to new collectors immediately
Show answer

Answer: D. A StateFlow, because it always holds the latest value and gives it to new collectors immediately

Screen state should always have a current value, and a screen that starts collecting after rotation must get it right away. StateFlow holds one value, replays it to new collectors and skips equal values. Channels or SharedFlows suit one-off events, but they lose the current state for late collectors.

13. collectAsStateWithLifecycle benefit

Why is collectAsStateWithLifecycle() usually preferred over collectAsState() when a Compose screen on Android reads a Flow from a ViewModel?

  1. A.It stops collecting when the lifecycle drops below STARTED, such as when the app goes to the background, saving work and resources
  2. B.It converts the Flow into LiveData under the hood
  3. C.It turns a cold Flow into a hot one
  4. D.It is the only way to read a StateFlow from Compose; collectAsState works only with LiveData and cold flows built with flow { }
Show answer

Answer: A. It stops collecting when the lifecycle drops below STARTED, such as when the app goes to the background, saving work and resources

collectAsState keeps collecting as long as the composable is in the composition, even when the app is in the background. collectAsStateWithLifecycle ties collection to the Android lifecycle, so upstream work such as location or database queries can pause. collectAsState still works; it is just less efficient on Android.

14. viewModelScope after the screen closes

What happens to coroutines launched in viewModelScope when the user leaves the screen for good and the ViewModel is cleared?

  1. A.They keep running until they finish
  2. B.They are moved to GlobalScope
  3. C.They pause and resume when the screen opens again
  4. D.They are cancelled automatically
Show answer

Answer: D. They are cancelled automatically

viewModelScope is cancelled when the ViewModel is cleared (onCleared), which cancels all of its child coroutines. This prevents leaks and wasted work for a screen that no longer exists. Work that must outlive the screen, like an upload, belongs in WorkManager or an application-level scope.

15. Modelling loading and error

A screen can be loading, show a list, or show an error message. Which Kotlin model makes impossible combinations unrepresentable?

  1. A.Three Boolean flags: isLoading, hasError, hasData
  2. B.A String field that holds "loading", "error" or "data"
  3. C.A nullable list plus a separate nullable error
  4. D.A sealed interface with Loading, Content(items) and Error(msg)
Show answer

Answer: D. A sealed interface with Loading, Content(items) and Error(msg)

Separate flags allow nonsense like loading and error both true. A sealed hierarchy holds exactly one state at a time, each with only the data it needs, and when forces the UI to handle every case.

16. When const val is allowed

Which declaration compiles?

  1. A.const val MAX_RETRIES = 3 at top level
  2. B.const val start = System.currentTimeMillis()
  3. C.const val user = User("a") in an object
  4. D.const var LIMIT = 10 at top level
Show answer

Answer: A. const val MAX_RETRIES = 3 at top level

const val must be a compile-time constant of a primitive type or String, declared at top level or in an object or companion object. Its value is inlined at use sites. Values computed at runtime need plain val.

17. Smart cast refused

Why does this fail to compile, and what is a common fix?

class User { var name: String? = null }

fun show(user: User) {
    if (user.name != null) {
        println(user.name.length)
    }
}
  1. A.Strings can never be smart cast in Kotlin code
  2. B.name is a var that may change; copy it to a local val
  3. C.The if statement needs an else branch to compile
  4. D.length is not a property that exists on String?
Show answer

Answer: B. name is a var that may change; copy it to a local val

The compiler only smart casts values it knows cannot change between the check and the use. A var property (or one with a custom getter or from another module) might change. Use val n = user.name, or user.name?.let { … }.

18. Thread safety of by lazy

Two threads read a "by lazy" property at the same moment for the first time. With the default mode, what happens?

  1. A.Both threads run the initializer and one value wins
  2. B.The second thread gets null
  3. C.It throws a ConcurrentModificationException
  4. D.The initializer runs once; the other thread waits for it
Show answer

Answer: D. The initializer runs once; the other thread waits for it

The default LazyThreadSafetyMode.SYNCHRONIZED uses a lock so the block runs exactly once. PUBLICATION allows multiple runs but one value is kept, and NONE skips locking for single-thread use such as UI-only code.

19. Giving up after 3 seconds

You want to try fetching a location for up to 3 seconds and fall back to the last known one. Which call fits?

  1. A.withTimeout(3000) { fetch() } ?: lastKnown
  2. B.delay(3000); fetch() ?: lastKnown
  3. C.withTimeoutOrNull(3000) { fetch() } ?: lastKnown
  4. D.Thread.sleep(3000) then fetch()
Show answer

Answer: C. withTimeoutOrNull(3000) { fetch() } ?: lastKnown

withTimeoutOrNull cancels the block after the time limit and returns null, which pairs well with ?:. withTimeout instead throws TimeoutCancellationException, which you would need to catch.

20. Coroutine stopped silently

A respawn coroutine never finishes. The object calls gameObject.SetActive(false) right after starting it. What happened?

  1. A.Deactivating the GameObject stops all coroutines running on its MonoBehaviours
  2. B.Coroutines only run while the object is visible to the camera
  3. C.SetActive pauses coroutines and resumes them later from the start
  4. D.The coroutine finished, but Unity hides its output
Show answer

Answer: A. Deactivating the GameObject stops all coroutines running on its MonoBehaviours

Coroutines are owned by the MonoBehaviour that started them. Deactivating the GameObject or destroying the component stops them, and reactivating does not restart them. Note that only setting enabled = false on the script does not stop them. Run the timer on a manager object that stays active instead.

21. When yield return null resumes

A coroutine runs yield return null. When does it continue?

  1. A.Immediately, later in the same frame
  2. B.After the next FixedUpdate step only
  3. C.After exactly 16.67 ms of real time
  4. D.Next frame, after that frame's Update calls
Show answer

Answer: D. Next frame, after that frame's Update calls

yield return null waits one frame; Unity resumes the coroutine after all Update methods run in the next frame. WaitForFixedUpdate and WaitForEndOfFrame resume at other points in the loop.

22. Reading a Boolean string

Which profile does this search return?

("java" OR "kotlin") AND "spring" NOT "intern"
  1. A.A Kotlin developer who uses Spring, not an intern
  2. B.A Java intern who uses Spring
  3. C.A Java developer with no Spring experience
  4. D.A Python developer who uses Spring
Show answer

Answer: A. A Kotlin developer who uses Spring, not an intern

The profile must contain Java or Kotlin, and must contain Spring, and must not contain "intern". Only the Kotlin developer using Spring, with no intern mention, meets all three conditions.

Kotlin interview questions for experienced candidates hard

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?

  1. A.Compose always recomposes every item on every frame
  2. B.LazyColumn does not support keys
  3. C.The parameter type is considered unstable, so Compose cannot safely skip the item; use immutable collections or stable types and pass keys
  4. 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)
    }
  }
}
  1. A.viewModelScope is never cancelled
  2. 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
  3. C.A while (true) loop can never be cancelled in Kotlin
  4. 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)
  1. A.false
  2. B.It does not compile
  3. C.It throws at runtime
  4. 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?
  1. A.Without reified, T is erased and "is T" is not allowed
  2. B.reified makes the function run on a background thread
  3. C.inline is required for any generic function
  4. 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?

  1. A.It runs the coroutine on a background thread for speed
  2. B.It makes the coroutine ignore any cancellation request
  3. C.Already on main, the code runs now instead of being queued
  4. 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
}
  1. A.finally blocks never run at all inside coroutines
  2. B.delay() inside a try block disables cancellation
  3. C.Suspend calls throw once cancelled; use NonCancellable
  4. 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?

  1. A.You cannot suspend inside synchronized; use Mutex.withLock
  2. B.synchronized makes the code run on the main thread
  3. C.synchronized only works with Java classes
  4. 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?

  1. A.Use runBlocking and Thread.sleep(30_000) in the test
  2. B.Use runTest, where delays run on virtual time
  3. C.Remove the delay from the production code for tests
  4. 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?

  1. A.Failed values silently fall through and return null
  2. B.The analyzer reports a compile error because the switch is not exhaustive
  3. C.It compiles, and a runtime error appears only when Failed occurs
  4. 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
  1. A.Only Java or Kotlin people who also have Spring
  2. B.Anyone with Java, plus Kotlin people who also have Spring
  3. C.Only people with all three words
  4. 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.

Ready to test yourself?

Take a 15-question Kotlin MCQ test with a score at the end, or practise the full Kotlin question bank and earn points on the leaderboard.