Tutorials

    Kotlin Coroutines Interview Guide

    A
    Anshul GuptaJul 8, 2026 · 9 min read
    102
    Kotlin Coroutines Interview Guide

    Q1. What is the difference between blocking a thread and suspending a coroutine?

    Answer

    Blocking a thread means the thread is occupied and cannot perform any other work until the current operation finishes. Even if the task is simply waiting (for example, waiting for a network response), the thread remains unavailable.

    Suspending a coroutine is different. The coroutine pauses its execution, stores its execution state in a Continuation, and releases the underlying thread back to the dispatcher. That thread can immediately execute other coroutines. When the suspended operation completes, the dispatcher resumes the coroutine on any available thread.

    Q2. Can a suspended coroutine resume on a different thread?

    Answer

    Yes.

    When a coroutine suspends, it releases its current thread. Later, when it resumes, the dispatcher schedules it on whichever suitable thread is available. Unless the dispatcher enforces thread affinity (such as Dispatchers.Main), the coroutine does not need to resume on the same thread.

    Q3. What information does a Continuation store?

    Answer

    A Continuation stores everything required to resume execution:

    • Current execution state

    • Current label (state machine position)

    • Local variables that are required after the suspension point

    • Result or exception needed to continue execution

    The compiler only stores variables that are actually needed after resumption, making continuations memory efficient.

    Q4. Does every suspend function create a state machine?

    Answer

    No.

    A suspend function only generates a compiler-created state machine when it contains at least one suspension point such as:

    • delay()

    • await()

    • withContext()

    • yield()

    • receive()

    • collect()

    If there are no suspension points, the compiler usually doesn't need to generate multiple states.

    Q5. Does a coroutine always have a thread?

    Answer

    No.

    A coroutine only owns a thread while it is actively executing.

    When suspended, it has no thread attached to it. Only its continuation exists in memory. When the coroutine resumes, the dispatcher assigns an available thread.

    Q6. When should you use launch, async and withContext?

    launch

    Use when you want to start independent work and don't need a result.

    Example:

    • Fire-and-forget analytics

    • Logging

    • Background cleanup

    async

    Use when the coroutine produces a result that will be consumed later.

    The result is represented by a Deferred<T> and retrieved using await().

    withContext

    Use when you want the same coroutine to temporarily execute on another dispatcher and continue only after that work completes.

    Unlike launch, it does not create a new coroutine.

    Q7. What are the possible outputs?

    launch {
        println("A")
    }
    
    println("B")
    
    kotlin

    Answer

    Possible outputs:

    AB
    
    code

    or

    BA
    
    code

    launch starts another coroutine, so scheduling determines which print executes first.

    Q8. Will async block execution?

    val result = async {
        delay(1000)
        10
    }
    
    println("Done")
    
    kotlin

    Answer

    No.

    async immediately returns a Deferred. Since await() is never called, "Done" prints immediately.

    Q9. Why doesn't this print 10?

    val result = async {
        10
    }
    
    println(result)
    
    kotlin

    Answer

    Because result is a Deferred<Int>, not an Int.

    The value is obtained only after calling:

    result.await()
    
    kotlin

    Q10. Why is await() a suspend function?

    Answer

    Because if the async computation hasn't completed, the current coroutine must suspend until the result becomes available.

    It suspends the coroutine—not the thread.

    Q11. What is CoroutineContext?

    Answer

    CoroutineContext is the execution environment of a coroutine.

    It contains everything required for coroutine execution:

    • Job

    • Dispatcher

    • CoroutineName

    • CoroutineExceptionHandler

    It determines how and where the coroutine executes.

    Q12. Which dispatcher is used?

    Dispatchers.IO +
    CoroutineName("Sync") +
    Dispatchers.Default
    
    kotlin

    Answer

    Dispatchers.Default

    When two elements with the same key exist, the last one replaces the previous one.

    Q13. Where does a child coroutine get its dispatcher?

    Answer

    If a child coroutine doesn't specify a dispatcher, it inherits its parent's dispatcher through CoroutineContext inheritance.

    Q14. Does a child share the same Job?

    Answer

    No.

    Each coroutine has its own Job.

    The child Job becomes a child of the parent's Job, creating a hierarchy.

    Q15. Why is CoroutineContext a typed map?

    Answer

    Instead of using Map<String, Any>, Kotlin uses typed keys.

    Advantages:

    • Compile-time type safety

    • No string typos

    • No unsafe casting

    • Easy extensibility for new context elements

    Example:

    coroutineContext[Job]
    
    kotlin

    always returns a Job?.

    Q16. Explain Structured Concurrency.

    Answer

    Structured concurrency means every coroutine belongs to a parent scope.

    The parent controls:

    • lifecycle

    • cancellation

    • exception propagation

    No coroutine should outlive the scope that created it.

    Q17. What happens if Child B throws an exception?

    Parent
    ├── Child A
    ├── Child B
    └── Child C
    
    code

    Answer

    Sequence:

    1. Child B throws an exception.

    2. Parent receives the exception.

    3. Parent cancels Child A.

    4. Parent cancels Child C.

    5. Parent completes exceptionally.

    Q18. Why do exceptions propagate upward but cancellation propagates downward?

    Answer

    Exceptions represent failures that must be reported to the parent so it can decide how to handle them.

    Cancellation is a control signal from parent to children requesting them to stop.

    Q19. When should SupervisorJob be used?

    Answer

    Use SupervisorJob when child coroutines are independent.

    Example:

    Loading a homepage:

    • Profile API

    • Wallet API

    • Offers API

    If Offers fails, Profile and Wallet should still load successfully.

    Q20. What happens when a SupervisorJob is cancelled?

    Answer

    Its children are also cancelled.

    SupervisorJob only changes exception propagation from child to siblings. Parent cancellation still propagates downward.

    Q21. Why do we have Dispatchers.Default and Dispatchers.IO?

    Answer

    Dispatchers.Default is optimized for CPU-intensive work.

    Dispatchers.IO is optimized for tasks that spend most of their time waiting (network, disk, database).

    Separating them prevents CPU-bound tasks from starving IO work.

    Q22. Why doesn't Default create 100 threads on an 8-core CPU?

    Answer

    Only about eight CPU-bound threads can execute simultaneously on an 8-core machine.

    Creating many more threads increases context switching without improving throughput.

    Therefore, Dispatchers.Default keeps its thread count close to the available CPU cores.

    Q23. 1000 coroutines call delay(). How many threads are blocked?

    Answer

    Very few (or none because of delay() itself).

    delay() suspends the coroutine, stores its continuation, and releases the thread back to the dispatcher.

    The waiting coroutines exist as continuations in memory rather than occupying threads.

    Q24. Why can Dispatchers.IO have more threads?

    Answer

    IO tasks spend much of their lifetime waiting.

    Additional threads improve throughput because while one thread waits for IO, another can execute useful work.

    Q25. Can an IO coroutine resume on another IO thread?

    Answer

    Yes.

    After suspension, the dispatcher schedules the coroutine on any available worker thread.

    Q26. launch vs withContext

    launch

    Creates a new coroutine.

    withContext

    Does not create a new coroutine.

    It simply switches the current coroutine's dispatcher temporarily.

    Q27. How many coroutines are created?

    launch {
    
        withContext(IO) {}
    
        withContext(Default) {}
    
    }
    
    kotlin

    Answer

    One.

    Only launch creates a coroutine.

    Both withContext calls reuse the same coroutine.

    Q28. Why is withContext a suspend function?

    Answer

    Because the current coroutine must pause while it switches execution to another dispatcher and then resume once the block finishes.

    Q29. Which thread executes after withContext?

    viewModelScope.launch {
    
        withContext(IO) {}
    
        println()
    }
    
    kotlin

    Answer

    The original dispatcher.

    withContext restores the previous context before continuing execution.

    Q30. Why might job.cancel() not stop a CPU loop?

    Answer

    Cancellation in coroutines is cooperative.

    If the coroutine never reaches a suspension point or checks cancellation using ensureActive(), yield(), or isActive, it continues executing.

    Q31. Why do we need ensureActive(), yield() and isActive?

    Answer

    Because coroutine cancellation is cooperative rather than forceful.

    These APIs provide explicit points where the coroutine checks whether cancellation has been requested.

    Q32. Where should ensureActive() be placed?

    Answer

    Inside long-running loops or expensive computations so cancellation is detected promptly.

    Q33. Homepage APIs: Profile, Wallet, Offers. One fails.

    Answer

    Use supervisorScope or SupervisorJob.

    These APIs are independent and should not cancel one another.

    Q34. Money Transfer: Debit, Credit, Update Ledger.

    Answer

    Do not use SupervisorJob.

    These operations are dependent and must succeed together.

    Failure should cancel the entire operation.

    Q35. What's wrong here?

    viewModelScope.launch {
    
        launch(IO) {
            val user = api.getUser()
        }
    
        renderUI()
    }
    
    kotlin

    Answer

    renderUI() doesn't wait for getUser().

    The child coroutine executes independently, causing a race condition.

    Q36. When are callbacks still useful?

    Answer

    Callbacks are naturally suited for continuous event streams such as:

    • BLE events

    • MQTT messages

    • Location updates

    • Sensor data

    • Button clicks

    These represent ongoing events rather than a single result.

    Q37. What does the compiler generate for a suspend function?

    Answer

    Conceptually:

    fun login(
        continuation: Continuation<Unit>
    ): Any?
    
    kotlin

    The compiler transforms the function into a state machine.

    Each suspension point becomes a state identified by labels, and execution resumes using the continuation.

    Q38. Why doesn't Continuation store every variable?

    Answer

    The compiler performs analysis and stores only variables required after suspension.

    Unused variables become eligible for garbage collection.

    Q39. Where is execution state stored?

    Answer

    Inside the Continuation object.

    Q40. Who decides which thread executes a resumed coroutine?

    Answer

    The Dispatcher.

    Q41. Why can't a normal function call a suspend function?

    Answer

    Suspend functions require a Continuation so they can suspend and later resume execution.

    Normal functions have no continuation and therefore cannot support suspension.

    Q42. Why doesn't delay() block a thread?

    Answer

    delay() registers a timer, suspends the coroutine, stores its continuation, and immediately releases the thread.

    When the timer expires, the dispatcher resumes the continuation.

    Q43. Can multiple coroutines execute on the same thread?

    Answer

    Yes.

    Different coroutines can execute on the same thread at different times.

    The dispatcher schedules them as threads become available.

    Q44. Can one coroutine execute on multiple threads?

    Answer

    Yes.

    After each suspension, the dispatcher may resume the coroutine on a different thread.

    Q45. What is the biggest misconception about coroutines?

    Answer

    The biggest misconception is that a coroutine is a lightweight thread.

    A coroutine is not a thread.

    It is a unit of work whose execution is scheduled by a dispatcher. It only occupies a thread while actively executing and releases it whenever it suspends.

    Coroutines
    Was this helpful?

    Comments (0)

    No comments yet. Be the first to share your thoughts!

    Leave a Comment

    Get the latest updates

    Join our newsletter and never miss an article.

    By subscribing, you agree to our Privacy Policy and consent to receive updates.

    Ready to Start Learning?

    Join thousands of students building their dream careers

    Explore Courses