Deep Dive into Kotlin Coroutines, Threads, and Suspension

This is a summary of all the topics I discussed in this coroutines series. Deep dive in Kotlin coroutine series:
What is suspension in coroutines and how it works?
Why coroutines is so popular why there was no solution like this before in Android?
Coroutines vs. Callbacks: The Similarity
What is Dispatcher, Thread and Threadpool?
1. What is a Coroutine?
A coroutine is a concurrency design pattern used to simplify code that executes asynchronously. In Kotlin, coroutines allow you to write asynchronous code in a sequential style. Unlike traditional threads, which are expensive in terms of resources and managed by the operating system, coroutines are lightweight and managed by the programming language runtime itself.
In Kotlin, coroutines are built on top of a framework that allows for cooperative multitasking. This means a coroutine can pause its execution (suspend) without blocking a thread, and then resume from where it left off.
Here’s a basic example of how suspension and resumption work:
import kotlinx.coroutines.*
fun main() = runBlocking {
println("Start")
val job = launch {
println("Coroutine started")
delay(1000L) // This is a suspending function
println("Coroutine resumed after delay")
}
println("Doing something else while coroutine is suspended")
job.join() // Wait for the coroutine to finish
println("End")
}
Step-by-Step Explanation
Start of Coroutine:
- The coroutine is launched inside the
runBlockingscope, which is a special coroutine builder that blocks the current thread until all coroutines inside it are complete.
- The coroutine is launched inside the
Suspending with
delay():The
delay(1000L)function is a suspending function. When the coroutine reaches this point, it suspends its execution. However, the thread is not blocked; it is free to do other work or go idle.Under the hood, the coroutine's current state (where it is in the code, local variables, etc.) is saved. The coroutine is now suspended.
Executing Other Code:
- While the coroutine is suspended, the
println("Doing something else while coroutine is suspended")statement runs. This demonstrates that other code can execute while the coroutine is paused.
- While the coroutine is suspended, the
Resuming the Coroutine:
After the delay (1 second in this case), the coroutine is resumed. It picks up exactly where it left off, with
println("Coroutine resumed after delay").The coroutine's saved state is restored, and execution continues.
Completion:
- Finally,
job.join()is called to ensure the main thread waits for the coroutine to finish. Once the coroutine is done, the program prints "End" and completes.
- Finally,
What's Happening Under the Hood?
State Machine:
- When you use suspending functions like
delay, the Kotlin compiler transforms the coroutine into a state machine. Each suspension point in the coroutine is converted into a state, and the compiler generates code to handle transitions between these states.
- When you use suspending functions like
Context Capture:
- The context (local variables, the current position in the code) is captured when a coroutine suspends. This captured state is stored in an object that the coroutine framework uses to resume the coroutine later.
Non-blocking Suspension:
- The thread running the coroutine does not block. Instead, the coroutine is suspended in a non-blocking way, allowing the thread to perform other tasks or wait for events.
Continuation:
- When a coroutine is resumed, the saved state (continuation) is restored, and the coroutine picks up where it left off. The framework ensures that the coroutine continues executing on the appropriate thread or thread pool, depending on the dispatcher in use.
Example with Custom Suspension
Let’s look at a more advanced example to understand custom suspension and resumption:
import kotlinx.coroutines.*
import kotlin.coroutines.*
fun main() = runBlocking {
println("Start")
val result = suspendCoroutine<String> { continuation ->
// Simulate an asynchronous operation
GlobalScope.launch {
delay(1000L)
continuation.resume("Hello from Coroutine!")
}
}
println(result)
println("End")
}
Custom Suspension Breakdown
suspendCoroutine:- This function allows you to manually suspend a coroutine and decide when to resume it. It takes a
Continuation<T>object as a parameter, which is used to resume the coroutine later.
- This function allows you to manually suspend a coroutine and decide when to resume it. It takes a
Simulated Asynchronous Operation:
- Inside
suspendCoroutine, an asynchronous operation is simulated usingGlobalScope.launchanddelay(1000L). After the delay,continuation.resume("Hello from Coroutine!")is called to resume the coroutine with the result"Hello from Coroutine!".
- Inside
Resumption:
- Once
resumeis called, the coroutine resumes its execution in the originalrunBlockingscope, and the result is printed.
- Once
1. How does a coroutine suspend a task? Why can’t a thread do the same?
Coroutine suspension is a mechanism where a coroutine pauses its execution at a "suspend" point without blocking the thread it's running on. During suspension, the state (including variables and the current position) is saved, and the coroutine can later resume from where it left off.
Threads, on the other hand, are lower-level constructs managed by the OS. When a thread "sleeps" or "waits," it blocks, preventing it from doing other work during that time. Coroutines do not block threads, allowing other tasks to run on the same thread or on another thread from the pool.
Threads themselves are tied to the OS, and they don't have the concept of suspension in the same way. When a thread "pauses" or "waits" (e.g., via
sleeporwait), it still consumes system resources. Coroutines, on the other hand, can release the thread they run on during suspension and pick up on any available thread when they resume, which makesThe suspend keyword in Kotlin tells the compiler to transform the coroutine into a state machine. This allows it to save the current state of execution and resume later.
2. Why didn’t anyone come up with a solution like coroutines before? Were there similar solutions?
Coroutines aren’t new—similar concepts have existed for decades in different forms (e.g., generators in Python, fibers, green threads, async-await in JavaScript, or goroutines in Go). However, Kotlin coroutines stand out because they provide an elegant, structured way to write asynchronous code that looks synchronous.
Android and Java didn’t historically have a built-in coroutine system, but other platforms like JavaScript (with
async/await) and Go (with lightweight goroutines) have similar mechanisms. The coroutine approach simplifies multi-threading by abstracting away low-level thread management.
3. How do pause and resume work in coroutines? (Coding overview)
- When you use
suspendfunctions, the coroutine pauses execution, saves its state, and continues after resuming. Here’s a coding overview:
import kotlinx.coroutines.*
fun main() = runBlocking {
println("Start")
// Launch a coroutine
launch {
println("Coroutine started")
delay(1000L) // Suspend the coroutine
println("Coroutine resumed after delay")
}
println("Doing other work while coroutine is suspended")
}
- When
delay(1000L)is called, the coroutine is suspended, but the thread is not blocked. The coroutine state is stored, and after 1 second, the coroutine resumes from the next line.
4. Is coroutine just like a callback under the hood?
Yes, coroutines work like callbacks under the hood, but in a structured and user-friendly way.
Instead of writing callback hell with nested callbacks, coroutines allow you to write code that appears synchronous but behaves asynchronously. Kotlin converts suspending points into continuations (which are like callbacks), and the framework manages resuming these continuations.
5. What role does the dispatcher play? Does it change the thread?
Dispatchers decide which thread or thread pool a coroutine should run on. They do change the thread in some cases, depending on the dispatcher used.
Dispatchers.Default is for CPU-bound tasks and uses a thread pool with as many threads as available CPU cores.
Dispatchers.IO is for I/O-bound tasks like file/network operations and uses a larger thread pool to handle more concurrent tasks.
Dispatchers.Main confines execution to the main UI thread (in Android), ideal for updating the UI.
6. Explanation of threads and thread pools
Thread: A thread is a unit of execution in a program. Threads run code, and they can run concurrently, sharing the same process memory. However, threads are heavy and switching between them (context switching) incurs overhead.
Thread pool: A thread pool manages a group of pre-created threads. Instead of creating and destroying threads frequently, tasks are assigned to these reusable threads. Thread pools are efficient and help control the number of concurrent tasks.
Dispatchers like Dispatchers.Default and Dispatchers.IO use thread pools to execute coroutines efficiently without blocking threads unnecessarily.

