You're reading for free via SUMIT KUMAR's Friend Link. Become a member to access the best of Medium.
Member-only story
Thread vs coroutines — Difference between thread and Kotlin coroutine in detail.
Imagine you are a chef preparing a delicious meal in a busy restaurant. The main thread represents you, the chef, responsible for managing the entire cooking process, including preparing dishes and serving them to customers.

If you are not a medium paid member user then use friend link to access the article without any paywall : )
Using Threads:
In a traditional scenario with threads, you have a single kitchen (a single thread) where all the cooking takes place. As orders (tasks) come in, you need to manage multiple pans simultaneously to prepare different dishes (concurrent tasks). Each time you start cooking a dish, you must wait for it to finish before moving on to the next, which can slow down the overall cooking process. Handling many pans (threads) at once can become overwhelming, and if not managed efficiently, it can lead to chaos in the kitchen.
Using Coroutines:
Now, let’s consider using coroutines in the kitchen. In this approach, you are still the main thread (chef) but with a twist. Instead of one kitchen, you have a set of designated cooking stations (coroutines) where you can prepare different dishes concurrently. Each station handles a specific task independently, such as chopping vegetables, boiling pasta, or grilling steak.
As orders come in, you start preparing each dish on the corresponding station (coroutine). Unlike before, you don’t have to wait for one dish to finish cooking before starting the next. Instead, you can pause the cooking process at each station when needed (e.g., when pasta needs to boil) and switch to another station to handle another task (e.g., grill the steak). This cooperative multitasking allows you to utilize your time more efficiently and deliver dishes faster to customers.
The Key Difference:
The key difference between threads and coroutines in this scenario is how they manage concurrent tasks. Threads represent a single kitchen with multiple pans, where each pan cooks one dish at a time, leading to potential delays. On the other hand, coroutines represent multiple cooking stations, enabling you to handle various tasks simultaneously and switch between them seamlessly, resulting in a more efficient cooking process and smoother customer service.
In real-life terms, coroutines allow for better resource management and more responsive task handling, making them a powerful and efficient solution for concurrent operations compared to traditional threads.
Now, Let’s come to the technical aspect
When it comes to concurrent programming, threads and coroutines are two widely used concepts. While both are used to perform tasks concurrently, they differ significantly in terms of their implementation, performance, and use cases. Now, we will explore the differences between threads and Kotlin coroutines in detail, with code examples and an illustrative image to help you understand the concepts better.
What is a Thread?
A thread is the smallest unit of a process that can be scheduled for execution. Threads are managed by the operating system (OS) and are heavyweight in nature. Each thread has its own memory stack, which makes context switching between threads expensive.

How Threads Work
Threads work by:
- Parallel Execution: Multiple threads can run concurrently, making use of multi-core CPUs.
- Context Switching: The CPU switches between threads to provide the illusion of parallelism.
- Blocking Operations: Threads can be blocked while waiting for resources, which can lead to inefficiency.
Thread Handling in Kotlin
Threads can be created and managed using:
Thread
class in Kotlin.Runnable
interface.- Thread pools with
ExecutorService
.
Executors in Threads
Executors in threading work as a higher-level abstraction over thread creation and management. They help manage a pool of threads and schedule tasks efficiently.
Characteristics of Threads:
- Managed by the OS.
- Heavyweight and resource-intensive.
- Context switching between threads is expensive.
- Each thread has its own memory stack.
- Suitable for CPU-bound tasks.
What is a Coroutine?
A coroutine is a lightweight, cooperative multitasking construct that is managed by the programming language or runtime, rather than the OS. Coroutines are not tied to a specific thread and can suspend execution without blocking the thread, making them more efficient for asynchronous programming. It is made of two different segments co and routines which means cooperation of functions(routines), function cooperates with each other which makes program or overall functionality more efficient in terms of resource management.

How Coroutines Work
- Suspension and Resumption: A coroutine can suspend its execution at a suspension point and resume later from where it left off.
- Non-blocking Nature: Unlike threads, coroutines do not block the thread; they release the thread for other tasks while waiting.
- Structured Concurrency: Coroutines can be grouped together in a structured way using
CoroutineScope
in Kotlin, ensuring that tasks are properly managed.
How Coroutines Use Threads and Switching?
Coroutines are designed to run on threads but offer a higher-level abstraction. They use the concept of continuations to suspend and resume tasks without blocking the thread. When a coroutine suspends, it saves its state, and the related thread is freed to execute other tasks. When resuming, the coroutine can continue execution on the same or a different thread based on the dispatcher used and the thread available in thread pool. This seamless switching is managed by Kotlin’s coroutine scheduler, which is part of the kotlinx.coroutines
library.
Threads Handling in Coroutines
Coroutines rely on threads for execution but abstract the complexity. They use a dispatcher to manage thread usage effectively. Multiple coroutines can run on a single thread or multiple threads based on the dispatcher configuration.
Dispatchers in Coroutines
Dispatchers determine which thread or thread pool a coroutine runs on:
Dispatchers.Main
: Runs on the main thread (for UI-related tasks).Dispatchers.IO
: Optimized for disk and network operations.Dispatchers.Default
: For CPU-intensive tasks.Dispatchers.Unconfined
: Starts in the caller thread but can be resumed in a different one.
What are Executors in Coroutines?
Executors are lower-level constructs used to manage thread pools. Coroutines internally use executors through dispatchers to manage thread allocation and execution. Kotlin’s newFixedThreadPoolContext
or Java's ExecutorService
can create thread pools to handle coroutines.
Characteristics of Coroutines:
- Managed by the programming language or runtime.
- Lightweight and efficient.
- Context switching is cheap.
- Share the same memory stack.
- Suitable for IO-bound and asynchronous tasks.
Key Differences Between Threads and Coroutines

Code Examples
Threads in Kotlin
Here’s an example of using threads in Kotlin:
fun main() {
println("Main thread starts: ${Thread.currentThread().name}")
val thread1 = Thread {
println("Thread 1 starts: ${Thread.currentThread().name}")
Thread.sleep(1000) // Simulate some work
println("Thread 1 ends: ${Thread.currentThread().name}")
}
val thread2 = Thread {
println("Thread 2 starts: ${Thread.currentThread().name}")
Thread.sleep(1000) // Simulate some work
println("Thread 2 ends: ${Thread.currentThread().name}")
}
thread1.start()
thread2.start()
thread1.join()
thread2.join()
println("Main thread ends: ${Thread.currentThread().name}")
}
Output:
Main thread starts: main
Thread 1 starts: Thread-0
Thread 2 starts: Thread-1
Thread 1 ends: Thread-0
Thread 2 ends: Thread-1
Main thread ends: main
In this example, two threads are created and executed concurrently. Each thread sleeps for 1 second to simulate work.
Coroutines in Kotlin
Now, let’s see how the same task can be achieved using Kotlin coroutines:
import kotlinx.coroutines.*
fun main() = runBlocking {
println("Main coroutine starts: ${Thread.currentThread().name}")
val job1 = launch {
println("Coroutine 1 starts: ${Thread.currentThread().name}")
delay(1000) // Simulate some work
println("Coroutine 1 ends: ${Thread.currentThread().name}")
}
val job2 = launch {
println("Coroutine 2 starts: ${Thread.currentThread().name}")
delay(1000) // Simulate some work
println("Coroutine 2 ends: ${Thread.currentThread().name}")
}
job1.join()
job2.join()
println("Main coroutine ends: ${Thread.currentThread().name}")
}
Output:
Main coroutine starts: main
Coroutine 1 starts: main
Coroutine 2 starts: main
Coroutine 1 ends: main
Coroutine 2 ends: main
Main coroutine ends: main
In this example, two coroutines are launched using the launch
builder. Notice that both coroutines run on the same thread (main
), demonstrating the lightweight nature of coroutines.
Visual Representation
Below is an image illustrating the difference between threads and coroutines:
Threads vs Coroutines Diagram


Explanation of the Diagram:
- Threads: Each thread has its own memory stack, and context switching involves saving and restoring the state of each thread, which is resource-intensive.
- Coroutines: Coroutines share the same memory stack and suspend execution without blocking the thread, making them lightweight and efficient.
When to Use Threads vs Coroutines
Use Threads:
- When you need true parallelism (e.g., CPU-bound tasks like image processing or mathematical computations).
- When working with legacy code that relies on threads.
Use Coroutines:
- When you need to perform asynchronous or IO-bound tasks (e.g., network requests, database operations).
- When you want to write cleaner and more maintainable concurrent code.
Summary
Threads and coroutines are both powerful tools for concurrent programming, but they serve different purposes. Threads are heavyweight and suitable for CPU-bound tasks, while coroutines are lightweight and ideal for IO-bound and asynchronous tasks. By understanding their differences and use cases, you can choose the right tool for your specific needs.
Thank you for reading this article. I hope the information provided has expanded your understanding and knowledge of this crucial concept. If you have any further questions or would like to contribute to the discussion, please feel free to leave your comments below. Your feedback and engagement are greatly appreciated. Thank you once again, and happy coding!
INIT KOTLIN : )