Skip to Content
Course content

69: Common Kotlin Interview Questions on Coroutines

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're running a busy professional kitchen. You've got one head chef (a Thread). Now, if that chef puts a cake in the oven and stands there staring at the timer for 30 minutes, the rest of the restaurant starves. That's blocking. Instead, a smart chef puts the cake in, sets a timer, and immediately starts chopping carrots. The cake is still "cooking" (the task is in progress), but the chef is free to do other things. When the timer goes off, the chef returns to the cake. This is suspending.

When you're in a Kotlin interview, the interviewer isn't just checking if you know the syntax; they want to know if you understand this "chef" logic. Let's map this directly to the concepts you'll be grilled on:

  • The Cake in the Oven: This is your suspend function. It tells the system, "I'm going to be waiting on something (like a network response), so feel free to use this thread for other tasks."
  • Chopping Carrots: This is another coroutine running on the same thread while the first one is suspended.
  • The Kitchen Stations: These are your Dispatchers. You wouldn't chop carrots on the grill, and you wouldn't fry a steak in the walk-in freezer. Dispatchers.Main is your plating station (UI), Dispatchers.IO is your pantry (disk/network), and Dispatchers.Default is your prep table (CPU-heavy logic).
  • The Shift Manager: This is the CoroutineScope. If the manager decides the kitchen is closing for the night, they cancel all active orders. If you use GlobalScope, it's like having a rogue chef who keeps cooking even after the restaurant has shut down—which is why I almost always tell juniors to avoid it.

The "Suspend" State Machine Secret

A common "gotcha" question is: "Does a suspend function block the thread?" The answer is a hard NO. But the follow-up is usually: "Then how does it actually work?"

I want you to remember this word: CPS (Continuation Passing Style). Under the hood, the Kotlin compiler transforms your suspend function into a state machine. It adds a hidden parameter called a Continuation to every suspend function. Think of the continuation as a "bookmark." When the function suspends, it saves its current state in that bookmark and returns control to the thread. When the result is ready, it uses that bookmark to jump right back to where it left off.

Choosing Between Launch and Async

Interviewers love to ask when to use launch versus async. It's a simple distinction, but people overthink it. Use launch for "fire and forget" tasks—like logging an event or updating a database where you don't need a return value. Use async when you actually need a result back.

// Fire and forget
scope.launch {
    repository.updateUserPreferences(prefs)
}

// I need this result to move forward
val userDeferred = scope.async {
    repository.fetchUser()
}
val user = userDeferred.await() // This is where we wait for the "cake" to finish

One pro tip: if you have three independent API calls, don't call them sequentially. Wrap each in async and then await() them all. You'll turn a 3-second wait into a 1-second wait.

Structured Concurrency and the Job Hierarchy

If you hear the term "Structured Concurrency," don't panic. It just means that coroutines have a parent-child relationship. If a parent job is cancelled, all its children are cancelled too. This prevents "zombie" coroutines from eating up memory in the background.

I've seen candidates struggle with the difference between Job and Deferred. Just remember: Deferred is a Job that returns a value. All Deferred objects are Jobs, but not all Jobs are Deferred.

The Dispatcher Decision Matrix

When asked which dispatcher to use, don't just say "IO for network." Be specific. I usually frame it like this:

  • Dispatchers.Main: Only for lightweight UI updates. If you do a heavy loop here, the app freezes.
  • Dispatchers.IO: Optimized for blocking I/O operations. It uses a shared pool of threads that can expand, because I/O tasks spend most of their time just waiting.
  • Dispatchers.Default: Optimized for CPU-intensive work (like parsing a massive JSON file or processing an image). It uses a pool of threads equal to the number of CPU cores.

If you're unsure, withContext(Dispatchers.IO) is your best friend for switching contexts mid-function without needing to start a whole new coroutine.




📋 Practical Task

Fixing the Blocking UI in a Mock Weather Fetcher

You've been handed a piece of code for a weather app. The developer who wrote it was new to coroutines and accidentally blocked the Main thread, causing the app to lag. Your task is to refactor the WeatherService to use the correct Dispatchers and the correct coroutine builders.


// CURRENT BROKEN CODE
class WeatherViewModel(val scope: CoroutineScope) {
    fun loadWeatherData() {
        // PROBLEM: This is running on the Main thread and blocking it!
        scope.launch(Dispatchers.Main) {
            val weather = fetchWeatherFromApi() // This is a heavy network call
            updateUI(weather)
        }
    }

    private fun fetchWeatherFromApi(): String {
        Thread.sleep(2000) // Simulating a blocking network call
        return "Sunny, 25°C"
    }

    private fun updateUI(data: String) {
        println("UI Updated with: $data")
    }
}

Your Requirements:

  1. Change fetchWeatherFromApi to be a suspend function.
  2. Ensure the network simulation doesn't block the thread (replace Thread.sleep with the appropriate coroutine delay).
  3. Use withContext inside the service function to ensure the network call happens on the IO dispatcher, regardless of where it was called from.
  4. Keep the updateUI call on the Main dispatcher.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.