ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Follow publication

Are Kotlin Coroutines Just Data Structures Using Continuations?

Sandeep Kella
ProAndroidDev
Published in
3 min readJan 3, 2025

Kotlin coroutines are often described as lightweight threads, enabling developers to write asynchronous, non-blocking code in a straightforward manner. However, under the hood, coroutines do not rely on threads in the traditional sense. This article delves into whether Kotlin coroutines are essentially just data structures leveraging continuations and explains how they function internally.

Threads vs. Coroutines: The Basics

Threads are the backbone of traditional concurrency in Java and many other programming languages. They are managed by the operating system, and switching between threads is an expensive operation. Each thread has its own stack, requiring significant memory and CPU resources.

Coroutines, on the other hand, are not bound to threads. Instead, they operate as cooperative tasks that can suspend and resume execution at specific points, enabling efficient use of resources. This raises an important question: If coroutines don’t directly use threads, what exactly are they?

The Role of Continuations

At their core, coroutines are built on a concept known as continuations. A continuation represents a point in a program where execution can be paused and later resumed. When a coroutine suspends, its current state (including local variables, the call stack, and program counter) is captured in a continuation object. This object can later be used to resume execution from the same point.

In Kotlin, the compiler transforms suspending functions into a state machine using these continuations. Each suspending function is essentially a function that takes an implicit Continuation parameter, which acts as a callback to resume execution. Here’s a simplified example:

suspend fun example() {
println("Start")
delay(1000) // suspension point
println("End")
}

Under the hood, the compiler translates this to something like:

fun example(continuation: Continuation<Unit>) {
println("Start")
// Save state and return control to the caller
continuation.resumeWith(Result.success(Unit))
println("End")
}

This mechanism ensures that coroutines can pause and resume without relying on traditional thread stacks.

Coroutines and Dispatchers

While coroutines themselves are independent of threads, they often interact with threads via dispatchers. Dispatchers determine where and how coroutines are executed. For example:

  • Dispatchers.Default: Uses a shared pool of threads for CPU-intensive tasks.
  • Dispatchers.IO: Optimized for offloading blocking I/O operations.
  • Dispatchers.Main: Confines execution to the main thread for UI updates.

However, these dispatchers do not fundamentally change the coroutine model. The coroutines are still implemented as data structures that utilize continuations. The threads provided by dispatchers are simply a mechanism for executing coroutine code, not an inherent part of the coroutine’s internal mechanics.

Advantages of the Coroutine Model

  1. Lightweight: Since coroutines do not allocate a separate thread stack, thousands of coroutines can run concurrently in the same application, whereas the number of threads is limited by system resources.
  2. Non-blocking: Coroutines can suspend execution without blocking the underlying thread, allowing other tasks to make progress.
  3. Efficient State Management: The use of continuations means that only the relevant state of a coroutine is preserved during suspension, minimizing overhead.
  4. Structured Concurrency: Kotlin coroutines enforce structured concurrency, making it easier to manage and reason about concurrent code.

Are Coroutines Just Data Structures?

While coroutines are often thought of as “lightweight threads,” they are better understood as data structures managing continuations. The key points are:

  • Coroutines are not threads, but they can run on threads.
  • A coroutine’s state is represented as a data structure that encapsulates the continuation logic.
  • Coroutines interact with threads via dispatchers, but they can also suspend and resume without requiring a thread to be tied up.

Thus, Kotlin coroutines are much more than mere threads or callbacks. They are a powerful abstraction that simplifies asynchronous programming while leveraging the underlying power of continuations and state machines.

Conclusion

Kotlin coroutines represent a shift in how we think about concurrency. By decoupling from threads and embracing continuations, coroutines provide an efficient, expressive, and lightweight mechanism for asynchronous programming. They may appear magical at first glance, but understanding their implementation as data structures using continuations helps demystify their inner workings. With this foundation, developers can make better use of coroutines to write clean, scalable, and performant code.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Published in ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Written by Sandeep Kella

Android dev by day, tech writer by night. Breaking down code so even my cat could understand it. Join me on Medium for fun, geeky, and practical Android tips!

Responses (1)

Write a response