Skip to content

update: rewriting coroutines basics for coroutines guide updates #4474

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

daniCsorbaJB
Copy link
Contributor

This PR rewrites the entire Coroutines basics guide to address user feedback and to align it structurally with the rest of the kotlinlang.org documentation.

It includes rewritten explanations, new examples, and more.

Related ticket: KT-56006
Related summary ticket for the entire coroutine guide rewrite efforts: KT-66329

@daniCsorbaJB daniCsorbaJB requested a review from dkhalanskyjb July 8, 2025 12:57
Copy link
Member

@murfel murfel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I unfortunately only lasted for 200 lines, though 🙂 I mostly left wording-related comments in the PR.

I do have thought on the general structure and I can produce more wording suggestions, just don't have more time to think them through today.


Let's dissect what this code does.
To include the `kotlinx.coroutines` library in your project, add the corresponding dependency configuration based on your build tool.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way to add kotlinx.coroutines to your project is to add something from the coroutine library and follow IDE's prompts.

Would be nice to provide it as the first option: "create an empty Kotlin project in Idea with the default settings, copy paste this example (todo) and follow the prompts (specify which, there should be two: first to include the kotlinx.coroutines dependency, then to include a coroutine import) to include"

I assume a dominant part of users who need to read this basic guide will be using Idea, so why not to make it easy for them. And for those who don't use Idea, that could be yet another way to advertise that everything works out of the box!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have several concerns about this:

  • Anything that's in the official documentation becomes a promise to keep. We need to make sure the IDE team considers this functionality reliable before making that promise on their behalf. Otherwise, we risk users getting frustrated and wasting time on functionality that only works part of the time.
  • Many (most?) users reading this are using Android Studio, which is based on IntelliJ, but is not fully controlled by us. We'd need to check that it works there as well.
  • People may try to transfer this advice to their own project, with a far more complex configuration than the empty one. Gradle configurations are just arbitrary code in general, so it's impossible for the IDE to figure out how to add dependencies reliably in all cases.

Copy link
Contributor Author

@daniCsorbaJB daniCsorbaJB Jul 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think we shouldn't cover something like this. Usually, we only include such information if it's an IDE specific plugin (for example, Kotlin Notebook)


## Create your first coroutines

Suspending functions are the basic building blocks for concurrency in Kotlin, but they can only run inside another suspending function or a coroutine.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: but is misleading, there isn't any contrast, these two statements are not contradicting to each other. Breaking it down into two equal sentences simplifies reading complexity:

Suspending functions are the basic building blocks for concurrency in Kotlin. A suspending function can only run inside another suspending function or a coroutine.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or is also misleading. I'd just remove or a coroutine entirely.

Threads run functions, from which one can call other functions; coroutines run suspend functions, from which one can call other (possibly suspend) functions. "Functions can only run inside other functions or threads" sounds very strange to me: they usually run in both.

## Create your first coroutines

Suspending functions are the basic building blocks for concurrency in Kotlin, but they can only run inside another suspending function or a coroutine.
A coroutine is a suspendable computation that runs on _threads_ and can run concurrently or in parallel with other coroutines.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very subtly introducing the notion of concurrent vs parallel. While I don't think it is the focus here to allow digressing into the difference, it would generally be nice to have maybe a separate link / section describing this?

I like this picture from Kotlin for Education lecture slides.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I feel like this intro or a subsection needs a similar picture for threads vs coroutines, but I couldn't find anything simple enough. Not comparing coroutines to threads, but showing how coroutines are run on top of threads.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To add to this,

concurrently or in parallel

I'd say all parallelism (executing tasks simultaneously in time) is concurrency (executing several tasks without waiting for one to finish before starting another), but not vice versa, so I'd phrase it a bit differently, like "and run concurrently with other coroutines (possibly in parallel)".

Copy link
Contributor Author

@daniCsorbaJB daniCsorbaJB Jul 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔

I like this picture from Kotlin for Education lecture slides.

I think we could introduce a similar picture at the very beginning of the document where we first talk about concurrency, I'll try to scetch up something and see how it flows! 👍

and run concurrently with other coroutines (possibly in parallel)

I like it — let's go with that version!

Copy link
Member

@murfel murfel Jul 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re pictures - just to clarify, I mentioned two pictures:

  1. coroutines vs threads (in terms of how coroutines run on threads)
  2. concurrent vs parallel

I feel like the first one is more needed than the second one, although both would be nice to show up at some point (maybe in other sections)

In any case I'd love to see what you come up with!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"on threads" here seems redundant to me, given the more elaborate explanation just below.

A coroutine is a suspendable computation that runs on _threads_ and can run concurrently or in parallel with other coroutines.

On the JVM, all concurrent code, such as coroutines, runs on threads, which are managed by the operating system.
Coroutines can suspend their execution instead of blocking a thread, which lets you run many tasks with less overhead.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I suggest a more specific example illustrating the same concept ("coroutines are effective").

While one coroutine is suspended, for example, waiting for a network package to arrive, another coroutine can execute on the same thread, ensuring effective resource utilization.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, sounds like a great idea — I like it! 👍 Let's add it

6. Combine these pieces to run multiple coroutines at the same time on a shared pool of threads:

```kotlin
// Imports the coroutines library
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm used to seeing imperative (import instead of imports) in comments and docs: import the library, define something, launch something else.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using third person in docs for code comments is part of the style guide, see here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh. I still hold that the third-person "imports, defines, launches" are a bit annoying. What everyone thinks?

Is there any room to wiggle on this one? Since it's not documentation per se, it's a tutorial / guide. Also, current guide uses a mix of imperative and third person.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can give instructions as code comments like "// Replace this part with your dependencies"
Or we can give additional info like "// Main.kt "
But in this context, I don't see a reason to deviate from the style here. 🤔 We want to ensure that users have the same experience when interacting with documentation, regardless of the topic.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm reading these comments as an instruction to the reader on what to do: you should import, then you should run the coroutines, etc.

I just want to register my disagreement, and agree to disagree.

Comment on lines +199 to +200
// Runs code in the coroutine scope
println("This runs while the coroutine is active")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what is meant by "This runs while the coroutine is active". This wording ("while ... active") misleads me into thinking that this println will be repeated multiples times while other things are running.

What does this bit aims to demonstrates?

Copy link
Contributor Author

@daniCsorbaJB daniCsorbaJB Jul 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm 🤔
I wanted to demonstrate how .launch() is not blocking the scope and that this part runs/finishes before those.
The issue is that I didn't want to go into too much detail here because we haven't introduced terms like child coroutines...

Looking at it, since we have a similar example demonstrating this behavior further down in the Coroutine scope and structured concurrency we could modify that part to something else, like "This runs alongside the launched coroutine".

Copy link
Member

@murfel murfel Jul 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something along the lines of:
"This print runs after launching the two coroutines above, concurrently with them, without waiting for them to complete."

Coroutines follow a principle of _structured concurrency_, which means new coroutines can only be launched in a specific [`CoroutineScope`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/) that defines their lifecycle.

When you work with multiple coroutines, you often need to run many tasks at once, cancel them when they are no longer needed, and handle errors safely.
Without clear structure, you risk leaving coroutines running longer than needed, wasting resources.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One also risks not having error propagation. I'd say leave out this sentence completely, since it's basically the negation of the previous one.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Alternatively, it could be enhanced by mentioning that not handling errors properly may lead to the program entering an inconsistent state. Then, this would be not just a negation but an elaboration in specific terms on what dangers structured concurrency saves you from.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed it 👍


## Coroutine scope and structured concurrency

Coroutines follow a principle of _structured concurrency_, which means new coroutines can only be launched in a specific [`CoroutineScope`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/) that defines their lifecycle.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While "new coroutines can only be launched in a specific CoroutineScope" is true, it is only a property and also an implementation detail of structured concurrency.

For me, structured concurrency means that coroutines (computations) are arranged into a forest of trees (coroutines form parent-child relationships, and multiple coroutines can be the roots of computations) which allows for (enables) cancellation support and error propagation.

Structured concurrency is a very big concept, and the definition should be grand and catchy.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then introducing CoroutineScope could go like this:

(Insert a sentence here to ligate structured concurrency to CoroutineScop). Each coroutine is launched within a single CoroutineScope that defines/manages its lifecycle.

I'm not sure about the word lifecycle, too. Does it mean structuring + cancellation + error in this context? If so, maybe it's worth elaborating that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a very good point... let's restructure it, what do you think of something like this:
In this version we first introduce the parent-child relationships — also we focus on the cancellation part as well then we tie it all up with the part that to ensure that all coroutines are launched from a CoroutineScope.

When you run many coroutines in an application, you need a way to manage them as a group.
Kotlin coroutines rely on a principle called structured concurrency to provide this structure.

This principle connects coroutines into a hierarchy of parent and child tasks that share the same lifecycle.
A parent coroutine waits for its children to complete before finishing.
If the parent coroutine fails or is canceled, all its child coroutines are canceled too.
Keeping coroutines connected in this way makes cancellation, error handling, and resource cleanup predictable and safe.

To keep this structure, new coroutines can only be launched in a CoroutineScope that defines and manages their lifecycle.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the word lifecycle as it is used as a new term, without being properly introduced and defined. I still don't know what exactly you mean by lifecycle?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, what do you think about adding this before the sentence starting with "A parent coroutine..."?

A coroutine’s lifecycle is the period from its start until it completes, fails, or is canceled.


Coroutines follow a principle of _structured concurrency_, which means new coroutines can only be launched in a specific [`CoroutineScope`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/) that defines their lifecycle.

When you work with multiple coroutines, you often need to run many tasks at once, cancel them when they are no longer needed, and handle errors safely.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's another way around. You utilize coroutines when you need to run many tasks, cancel them, and handle errors. Running tasks is the goal, while coroutines are just the means to do so.

Copy link
Contributor Author

@daniCsorbaJB daniCsorbaJB Jul 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great point 👍 I revised this section, see the comment where we talk about the introduction of structured concurrency.


You will see the following result:
This example doesn’t use concurrency yet, but by marking the functions with the `suspend` keyword,
you allow them to be used in concurrent code later.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite. suspend functions allow using concurrency inside them, but you can call non-suspend functions from suspend ones freely. Concurrent code is free to use everything, but can only be used from inside suspend functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about:
This example doesn’t use concurrency yet, but by marking the functions with the suspend keyword,
you allow them to call other suspending functions and run concurrent code inside.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that works.

## Create your first coroutines

Suspending functions are the basic building blocks for concurrency in Kotlin, but they can only run inside another suspending function or a coroutine.
A coroutine is a suspendable computation that runs on _threads_ and can run concurrently or in parallel with other coroutines.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To add to this,

concurrently or in parallel

I'd say all parallelism (executing tasks simultaneously in time) is concurrency (executing several tasks without waiting for one to finish before starting another), but not vice versa, so I'd phrase it a bit differently, like "and run concurrently with other coroutines (possibly in parallel)".

often see `runBlocking` used like that at the very top-level of the application and quite rarely inside the real code,
as threads are expensive resources and blocking them is inefficient and is often not desired.
> You can display coroutine names next to thread names in the output of your code for additional information.
> In IntelliJ IDEA, right-click the `Run` icon: ![Run icon](run-icon.png){width=30} next to your `main()` function, select `Modify Run Configuration...`, and add `-Dkotlinx.coroutines.debug` in `VM options`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unrelated to IDEA, though. You can just as well pass -Dkotlinx.coroutines.debug directly to Maven or Gradle in a console invocation or anywhere else. I think it makes sense to avoid mentioning IDEA here and make the advice universally applicable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea 👍
Let's go with:
You can display coroutine names next to thread names in the output of your code for additional information.
To do so, pass the -Dkotlinx.coroutines.debug VM option in your build tool or IDE run configuration.

> You can display coroutine names next to thread names in the output of your code for additional information.
> In IntelliJ IDEA, right-click the `Run` icon: ![Run icon](run-icon.png){width=30} next to your `main()` function, select `Modify Run Configuration...`, and add `-Dkotlinx.coroutines.debug` in `VM options`.
>
> See [Debug coroutines using IntelliJ IDEA — Tutorial](debug-coroutines-with-idea.md) for more information.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/topics/debugging.md is the file to link here, as it's the one describing kotlinx.coroutines.debug.

Comment on lines +134 to +135
> While you can mark the `main()` function as `suspend` in some projects, other projects or frameworks might not allow changing the `main()` function this way.
> In those cases, coroutine builder functions like [`runBlocking`](#runblocking) are the usual entry point for calling suspending code.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

runBlocking is the only such bridging function, and I'd shift the focus from main() specifically. I'm thinking of something like this:

While you can mark the main() function as suspend in some projects, it may not be possible when integrating coroutines with existing code or building the project around a framework. In that case, check the documentation of the framework to see if it provides the ability to call suspend functions already. If all else fails, it is possible to run suspend functions by blocking the current thread by using runBlocking.

A bit too verbose, but I think it's important to get the point across: after all, we are literally talking about entry points, nothing is possible without them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good approach 👍

3. Use the [`coroutineScope()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html) function to [create a `CoroutineScope`](#coroutine-scope-and-structured-concurrency) that groups your coroutines and controls their lifecycle:

```kotlin
suspend fun main() = coroutineScope {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that this series of examples follows the idea I've proposed of mentioning dispatchers early on, but as is, it's not quite the code that anyone would actually write. I was thinking of something like this (and could probably have been clearer about it):

suspend fun main() = withContext(Dispatchers.Default) {
    launch { greet() }
    println("This can run in parallel with the greeter coroutine")
}

This way, it's clear in which context all pieces of code inside main are running, including the println.

This example demonstrates simple multithreading with coroutines on a shared thread pool.

> Try running the example multiple times.
> You may notice that the output order and thread names may change each time you run the program.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They could have noticed that on step 4 already, by the way, as the launch'ed coroutine runs in parallel with the code that launched it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true 👍 I'd prefer to keep the tip at the bottom though since thats where the user can run the code on the website.

@dkhalanskyjb dkhalanskyjb self-requested a review July 9, 2025 10:21
Coroutines follow a principle of _structured concurrency_, which means new coroutines can only be launched in a specific [`CoroutineScope`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/) that defines their lifecycle.

When you work with multiple coroutines, you often need to run many tasks at once, cancel them when they are no longer needed, and handle errors safely.
Without clear structure, you risk leaving coroutines running longer than needed, wasting resources.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Alternatively, it could be enhanced by mentioning that not handling errors properly may lead to the program entering an inconsistent state. Then, this would be not just a negation but an elaboration in specific terms on what dangers structured concurrency saves you from.


Run the following code to get to your first working coroutine:
You can only call a suspending function from another suspending function or from a coroutine.
Even the `main()` function can be suspending:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why "even", though? The idea being highlighted here (I assume) is that suspend fun main is one of the ways of obtaining the "root" suspend function, which will then allow calling the other ones.


You will see the following result:
This example doesn’t use concurrency yet, but by marking the functions with the `suspend` keyword,
you allow them to be used in concurrent code later.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that works.

Hello
World!
```
While the `suspend` keyword is part of the Kotlin standard library, most coroutine features
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
While the `suspend` keyword is part of the Kotlin standard library, most coroutine features
While the `suspend` keyword is part of the Kotlin language itself, most coroutine features

The standard library does contain the basic building blocks used by kotlinx.coroutines (for example, the suspendCoroutine function), but keywords are not provided in the library.


## Create your first coroutines

Suspending functions are the basic building blocks for concurrency in Kotlin, but they can only run inside another suspending function or a coroutine.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or is also misleading. I'd just remove or a coroutine entirely.

Threads run functions, from which one can call other functions; coroutines run suspend functions, from which one can call other (possibly suspend) functions. "Functions can only run inside other functions or threads" sounds very strange to me: they usually run in both.


## Scope builder and concurrency
Use the [`.runBlocking()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html) coroutine builder function to create a coroutine scope in a blocking context.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Use the [`.runBlocking()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html) coroutine builder function to create a coroutine scope in a blocking context.
Use the [`runBlocking()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html) coroutine builder function to create a coroutine scope and block the current thread until the coroutine started in it finishes.

"Blocking context" is somewhat too abstracted from the practical consequences of using runBlocking.

Copy link
Contributor Author

@daniCsorbaJB daniCsorbaJB Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, this is much clearer 👍 I rewrote this a little because this way the next sentence almost repeats this sentence, what do you think? 🤔

Use the runBlocking() coroutine builder function to create a coroutine scope and block the current thread until
the coroutines launched in that scope finish.
Unlike other coroutine builders, runBlocking() doesn't use a shared thread pool.


A [coroutineScope][_coroutineScope] builder can be used inside any suspending function to perform multiple concurrent operations.
Let's launch two concurrent coroutines inside a `doWorld` suspending function:
`.runBlocking()` creates a new `CoroutineScope` for the code inside it.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`.runBlocking()` creates a new `CoroutineScope` for the code inside it.
`runBlocking()` creates a new `CoroutineScope` for the code inside it.

A [coroutineScope][_coroutineScope] builder can be used inside any suspending function to perform multiple concurrent operations.
Let's launch two concurrent coroutines inside a `doWorld` suspending function:
`.runBlocking()` creates a new `CoroutineScope` for the code inside it.
This lets you call suspending functions without marking the surrounding function as `suspend`, for example, in the `main()` function:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This lets you call suspending functions without marking the surrounding function as `suspend`, for example, in the `main()` function:
It lets you call suspending functions without marking the surrounding function as `suspend`, for example, in the `main()` function:

Where "it" = runBlocking.
Otherwise, I'm reading this as "this property of creating a new CoroutineScope allows calling suspend functions". That would be incorrect: the reason we can run suspend functions there is that runBlocking's block parameter has suspend in its type.


```kotlin
import kotlinx.coroutines.*

//sampleStart
// Sequentially executes doWorld followed by "Done"
// Runs the greet() function inside a blocking scope
fun main() = runBlocking {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indiscriminately using runBlocking is an antipattern that we want to discourage: #4412

Instead, we can formulate an example that explains when runBlocking is useful/unavoidable in real scenarios. It's something like this:

// a third-party library defines this interface that we must implement
interface Repository {
  fun readItem(): Int
}

object MyRepository: Repository {
  override fun readItem(): Int {
    // we can't call suspend functions here,
    // because the interface we must implement expects synchronous code.
    // But we want to use coroutines as the implementation detail!
    return runBlocking {
      myReadItem()
    }
  }
}

suspend fun myReadItem(): Int {
  delay(100.milliseconds)
  return 4
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the example - let's go with that

## Coroutine dispatchers

A [coroutine dispatcher](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/#) controls which thread or thread pool coroutines use for their execution.
Coroutines are not tied to a single thread.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Coroutines are not tied to a single thread.
Coroutines are not always tied to a single thread.

They may be tied to a single thread. That's occasionally necessary. For example, Dispatchers.Main ensures all code runs on the (single) UI thread.

@dkhalanskyjb dkhalanskyjb self-requested a review July 14, 2025 12:54
A [coroutine dispatcher](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/#) controls which thread or thread pool coroutines use for their execution.
Coroutines are not tied to a single thread.
They can pause on one thread and resume on another, depending on the dispatcher.
This lets you run many coroutines at the same time without blocking threads.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This lets you run many coroutines at the same time without blocking threads.
This lets you run many coroutines at the same time without allocating a separate thread for every coroutine.

That's the point of sharing threads. They can still be blocked: withContext(Dispatchers.IO) { readFile(path) } will block the OS thread that's reading the file, this is unavoidable.

A dispatcher works together with the [coroutine scope](#coroutine-scope-and-structured-concurrency) to define when coroutines run and where they run.
While the coroutine scope controls the coroutine’s lifecycle, the dispatcher controls which threads are used for execution.

> You don't have to create a dispatcher for every coroutine.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> You don't have to create a dispatcher for every coroutine.
> You don't have to specify a dispatcher for every coroutine.

Creating dispatchers is possible, but quite niche.


### Structured concurrency
To create a coroutine in Kotlin, you need a suspending function, a scope, a builder to start it, and a dispatcher to control which threads it uses:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Obviously under the hood, the coroutine has an associated dispatcher, but I'm wondering if it's still right to say that "to create a coroutine, you need a dispatcher"? After all, from the developer's perspective, you can happily omit it.


suspend fun main() = coroutineScope {
// Starts a coroutine with Dispatchers.Default
launch(Dispatchers.Default) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that there's no dispatcher to inherit, even the unparametrized version launches on Dispatchers.Default. I'm guessing you picked Default here because anything else would require too much storytelling to justify?

Might I suggest adding the word "explicitly" to the comment above this line, to make this more clear?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea! I'll add it 👍

}

suspend fun main() = coroutineScope {
// Starts a coroutine with Dispatchers.Default
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a question regarding the preposition: I've often heard it referred to as running on a Dispatcher (since that's what defines the shared pool of threads) rather than with a Dispatcher. Thoughts on this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 You are right - it feels off re-reading it. I like running on a Dispatcher as a general solution in this case and in similar cases 👍

Here I think either could work :

  1. Starts a coroutine that runs on Dispatchers.Default (I prefer this for its conciseness)
  2. Starts a coroutine using Dispatchers.Default to control where it runs/choose the thread pool etc.

launch { doWorld() }
println("Hello")
}
suspend fun main() = coroutineScope {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you share the reasoning behind this pattern / where I could learn more about it?


For more information, see [Coroutine context and dispatchers](coroutine-context-and-dispatchers.md)

## Comparing coroutines and JVM threads
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like it'd be a good place to reassure people about the fact that despite potentially running on different carrier threads, there's a clear happens-before relationship inside an individual coroutine, and that a coroutine -- even when hopping between threads -- is guaranteed to observe any preceding writes (i.e. that despite thread-hopping, that there are no "mutable shared state" issues)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea, what do you think about adding a tip before the link with something like this:

Even though coroutines can suspend and resume on different threads, values written before the coroutine suspends are still available within the same coroutine when it resumes.

```kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guessing that given the prev. discussion, this is also a place where runBlocking should go away.

{style="note"}


3. Use the [`coroutineScope()`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html) function to [create a `CoroutineScope`](#coroutine-scope-and-structured-concurrency) that groups your coroutines and controls their lifecycle:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@murfel raised an interesting point regarding this: for newcomers to Kotlin that go straight to learning coroutines, the mechanism with which coroutineScope provides a CoroutineScope inside it is unclear. A link to https://kotlinlang.org/docs/lambdas.html#function-literals-with-receiver could help with that.

A specific concern was that a user can attempt to manually refactor this function:

suspend fun foo() = coroutineScope {
  launch { println("1") }
  launch { println("2") }
}

into

suspend fun foo() = coroutineScope {
  launchThings()
}

suspend fun launchThings() {
  launch { println("1") }
  launch { println("2") }
}

Which will fail to compile with an error that's inscrutable to a newcomer: after all, the code is called from a coroutine scope, what's the matter?

Note: the IDE does (almost) correctly allow extracting this piece of code into

private fun CoroutineScope.launchThings(): Job {
    launch {
        println("ok")
    }
    return launch {
        println("ok2")
    }
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To further clarify the mechanism of lambda-with-receiver, It could be beneficial to additionally amend an inline comment that replicates what inlay hints can also show in the IDE, i.e.

suspend fun foo() = coroutineScope { // this: CoroutineScope

}

(if we wanted to be extra explicit, we could even make the calls to this.launch explicit, though I think that's potentially already too-much-noise territory)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants