# Things every Kotlin Developer should know about Coroutines. Part 2: CoroutineScope.

In my experience, `CoroutineScope` is one of the less understood parts of the coroutines library, even though it is one of the most important. In Android, we are spoiled with coroutine scopes kindly provided to us by lifecycle libraries, but for many developers, things quickly get confusing as soon as there is a need to create a custom `CoroutineScope`, or if they need to write coroutines in an environment that doesn't provide a pre-defined scope.

That said, by the end of this article, you will know everything you need to use the `CoroutineScope` comfortably and correctly.

## What is a CoroutineScope

### Declaration

First, we have to understand what a `CoroutineScope` is. There is no better place to look for the answers than its declaration:

```kotlin
public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}
```

As you can see, it is *a very simple interface*, but there is quite a lot to unpack here.

A `CoroutineScope` is just a simple wrapper for a `CoroutineContext`. In a technical sense, it is nothing more than a `CoroutineContext`, and the only reason it exists and has a different name is *to differentiate the intended purpose* of the two.

You can read more about this in Roman Elizarov's [blog post](https://elizarov.medium.com/coroutine-context-and-scope-c8b255d59055) on the topic. I like how in the beginning of his post he gives an example of this concept:

> *Different uses of physically near-identical things are usually accompanied by giving those things different names to emphasize the intended purpose. Depending on the use, seamen have a dozen or more words for a rope though it might materially be the same thing.*
> 

So why do we need to have this separate `CoroutineScope`?

### Intended purpose of the CoroutineScope

Coroutines *are very cheap* and we are encouraged to create them as we see fit. We can launch coroutines in different ways from many different places. Some might complete fast, and some might take a long time. Some might have multiple child coroutines of their own. And any of them can potentially fail.

In the early days of the coroutines library, we would *launch* coroutines in a fire-and-forget manner. However, if we would try to manage all those asynchronously running coroutines in an application that has components with a limited lifecycle, for example, a UI screen that could be closed by the user and should cancel all its work, the complexity of coroutine management would grow exponentially.

Therefore, at one point in its development, the coroutines library had a major ideology shift called *Structured Concurrency*. It aimed for all coroutines to be launched in a defined scope, which would allow for an easy way to manage cancellation, exception handling and not allow resource leaking.

And that is *the intended purpose* of a `CoroutineScope` - *to enforce structured concurrency*.

We will focus on structured concurrency and how to practically comply with its principles in Part 3 of this series. For now, however, there is still much to discuss about the `CoroutineScope` itself.

### Coroutine launchers

There are two ways to launch a new coroutine - with either `launch` or `async` function. Let's quickly look at their declaration.

```kotlin
public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job
```

```kotlin
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T>
```

As much as I would like to dissect every parameter and inner workings of these functions, I will not bore you with that. The main takeaway, which is essential to know and understand, is that these are *extension functions* on the `CoroutineScope` interface.

That is why we cannot launch a new coroutine without a scope. 

We will return to this concept throughout this article.

## Creating your own scopes

### Scope builder functions

There are two types of scope builder functions - suspending and non-suspending. Since we can only call suspending functions from a coroutine, we cannot use them to create our top-level scopes. Still, they are very useful, and we will discuss them later.

To create a top-level scope we can use one of the following builder functions:

- `MainScope()`
- `CoroutineScope(context: CoroutineContext)`

Notice that they are *functions* - in Kotlin, we often use the pascal case in function names if they build and return an object. That makes for a cleaner and more readable API:

```kotlin
// this is a function that returns an instance
// of CoroutineScope, not a constructor
val mainScope = MainScope() 
```

The main difference between these two scopes is that the `MainScope()` uses `Dispatchers.Main` for its coroutines, making it perfect for UI components, and the `CoroutineScope()` uses `Dispatchers.Default` by default.

Another difference is that `CoroutineScope()` takes in a `CoroutineContext` as a parameter.

If you have read Part 1 of this series, you should be familiar with `CoroutineContext` and how to use it.

Here are some examples:

```kotlin
val scope1 = CoroutineScope(SupervisorJob())

val scope2 = CoroutineScope(SupervisorJob() + Dispatchers.IO)

val scope3 = CoroutineScope(SupervisorJob() + CoroutineName("MyScope"))
```

> Note that we are using `SupervisorJob` instead of `Job`. It is an important concept regarding error handling, which we will discuss in-depth in Part 4 of this series.
> 

There are a couple of things to consider when creating a scope using the `CoroutineScope()` function.

The `context` parameter is mandatory. That said, if you don't pass a `Job` element, a default `Job()` will be created since a scope must have a `Job` to enforce structured concurrency. Also, if we don't pass a coroutine dispatcher, `Dispatchers.Default` will be used.

However, given that not every developer knows the inner workings of these functions, I find it convenient and more readable to define the `context` of my scopes explicitly:

```kotlin
// even though adding Dispatchers.Default is not necessary,
// I find it more readable to state every element
// of the context explicitly
val appScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
```

When using the `MainScope()` function, however, we don't need to specify the context - it automatically creates a context with a `SupervisorJob` and a `Dispatchers.Main`:

```kotlin
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
```

If you need to add an `Element` to the `MainScope` you can use the `plus` operator function, just like we would with a `CoroutineContext`:

```kotlin
val mainScope = MainScope() + CoroutineName("MainScope")
```

### Legacy convention

When `CoroutineScope` was first introduced into the coroutines library, the official documentation described another way to create scopes. And although the documentation was since updated to recommend a more readable and straightforward way to create scopes as described above, you might still stumble across the legacy way in some codebase.

The legacy way recommended creating coroutine scopes by implementing the `CoroutineScope` interface:

```kotlin
// standard implementation
class MyComponent : CoroutineScope {
    override val coroutineContext: CoroutineContext = SupervisorJob() + Dispatchers.Default
}
```

```kotlin
// implementation using delegation
class MainActivity : AppCompatActivity(), CoroutineScope by MainScope()
```

However, you should always use the non-legacy approach in your code, which is more straightforward to use.

## Suspending scoped concurrency

### Suspending scope builders

We are not done with scope builder functions just yet. We have looked at non-suspending ways to create coroutine scopes, but as I have mentioned, there are also suspending ones.

There are two, to be exact:

```kotlin
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R
```

```kotlin
public suspend fun <R> supervisorScope(block: suspend CoroutineScope.() -> R): R
```

There is also a third function that you are already familiar with, which is very similar:

```kotlin
public suspend fun <T> withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T
```

Although the implementation of `withContext()` is similar to that of `coroutineScope()`(but a bit more complicated), its intended purpose is to *switch context* within a suspending function, not to *provide a scope*. It might seem confusing, just as with `CoroutineScope` being the same thing as `CoroutineContext`, but once again - *the intended purpose is the important part*.

That said, we will not discuss the `withContext()` function and focus on the two functions that *explicitly provide a scope,* even though technically you can do the same things with `withContext()`, since all these functions take `block: suspend CoroutineScope.() -> T` as an argument and allow launching new coroutines inside this `block`.

With that out of the way, let's take a look at a practical example.

### Why do we need suspending scope builders

As you now know, the coroutine launcher functions `launch` and `async` are extension functions on the `CoroutineScope`. We cannot launch new coroutines outside of a coroutine scope. That also means we cannot launch new coroutines from suspending functions since they are regular functions that undergo a "minor" transformation during compilation.

However, there are certainly use cases where we would like to do that.

Here is an example:

```kotlin
val dataUrls = listOf("url1", "url2", "url3")

fun main() {
    runBlocking {
        val time = measureTimeMillis {
            downloadAllData(dataUrls)
                .forEach(::println)
        }
        println("Done in: $time ms")
    }
}

suspend fun downloadAllData(urls: List<String>): List<Data> {
    return urls.map { url -> downloadData(url) }
}

suspend fun downloadData(url: String): Data {
    return withContext(Dispatchers.IO) {
        delay(100)
        Data("I am data from: $url")
    }
}

data class Data(val content: String)

Output:
Data(content=I am data from: url1)
Data(content=I am data from: url2)
Data(content=I am data from: url3)
Done in: 329 ms
```

Predictably, we got our data downloaded sequentially in about `300ms`. Of course, we could add `async` to each call of `downloadData` in our top-level coroutine, but that would be detrimental to the quality and reusability of our code. We can do better than that.

With the help of suspending scope builders, we can load all our data concurrently inside the `downloadAllData` function:

```kotlin
suspend fun downloadAllData(urls: List<String>): List<Data> {
    return coroutineScope {
        urls.map { url -> async { downloadData(url) } }
            .map { deferred -> deferred.await() }
    }
}

Output:
Data(content=I am data from: url1)
Data(content=I am data from: url2)
Data(content=I am data from: url3)
Done in: 129 ms
```

An important thing to note about suspending scope builders is that they *return only when all their child coroutines have finished*. This *enforces structured concurrency* and adheres to one of the main ideologies of the coroutine library - that suspending functions *execute sequentially*, just like the regular code. So in the example above, even though we launch multiple new coroutines inside the `downloadAllData` function, it will only return when all the work is done, without leaking coroutines. We will talk more about it in the best practices section.

The `supervisorScope()` is the same as `coroutineScope()`, but uses a `SupervisorJob` for its context instead of a `Job`. We will discuss everything there is to know about using `Job` and `SupervisorJob` in Part 4 of this series dedicated to error handling.

## A few words about GlobalScope

This article would not be complete if we didn't mention the infamous `GlobalScope`. It had confused so many developers that in Kotlin 1.5 JetBrains marked it with `@DelicateCoroutinesApi`, which requires an opt-in to use it.

There are a couple of things you need to know about the `GlobalScope`, which should be easier to understand now that you know how scopes work and why we need them.

First of all, the `GlobalScope` *doesn't adhere to the structured concurrency principle*, which, as you now know, is a big deal in coroutines. It doesn't have any `Job` tied to it, and it is running during the whole lifetime of an application.

It might have its uses, but to use the `GlobalScope` correctly, you have to manage every coroutine launched inside it manually, keeping track of their jobs and not forgetting to `.join()` those jobs to other coroutines if your use case requires it.

You have to know what you are doing when using the `GlobalScope`, and even then, you might still introduce some subtle bugs if your attention slips.

So the recommendation is to *avoid it altogether*.

You can read more about this topic in [this post](https://elizarov.medium.com/the-reason-to-avoid-globalscope-835337445abc) by Roman Elizarov.

## Conventions and Best Practices

We have already discussed quite a few examples of using the `CoroutineScope` correctly. However, there are still some things that you should keep in mind.

First of all, as we have mentioned earlier - do not ever launch new unbounded coroutines from a suspending function. Nothing should be left running in the background when a suspending function returns.

You should never write code like this:

```kotlin
suspend fun launchWorker() {
    ... // any suspending code
    CoroutineScope(Dispatchers.IO).launch { 
        doWork()
    }
}
```

Even if you keep a job handle to this new coroutine - this implementation is very smelly. It will make your code unpredictable, and you will break the structured concurrency principle.

If you need to write a function that launches a new coroutine, the convention is to use *a regular extension function* on the `CoroutineScope` - just like the `launch` and `async` functions.

The correct way to implement the example above would be:

```kotlin
fun CoroutineScope.launchWorker() {
    launch {
        // switch the dispatcher inside doWork() if needed
        // as we have discussed in Part 1
        doWork()
    }
}
```

Now you can launch your custom coroutines safely inside the context of a scope.

This convention also follows the pattern that *launching a new coroutine returns immediately* while *suspending functions return only after all the work is completed*.

Another best practice for creating your own `CoroutineScope` is to keep it bound to the lifecycle of some component. For example, in Android, we have `viewModelScope` attached to the `ViewModel`, and `lifecycleScope` attached to the `LifecycleOwner`. As soon as these components are destroyed, their scopes are immediately cancelled, preventing leaking coroutines.

This is a good practice regardless of the platform. You might have an application-level `CoroutineScope`, which might not need explicit cancelling, but for any other use case, you should limit the lifecycle of your scopes:

```kotlin
class MyComponent() {
    val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
    ...
    fun onDestroy() {
        ...
        scope.cancel()
    }
}
```

Lastly, it is more clean and readable to use extension properties and functions like `isActive`, `cancel()`, and `ensureActive()` on your `CoroutineScope` not on the `CoroutineContext` even though they are the same thing. "Cancelling a scope" is immediately apparent, while "cancelling a context" occasionally might raise some questions just because of the naming.

## Conclusion

You might have noticed a trend that despite having a very flexible API, the coroutines library has a set of simple, straightforward recommendations and practices, which make using coroutines much less intimidating, especially when starting out.

Hopefully, after reading this article, using the `CoroutineScope` will be smooth sailing.

In Part 3, we will dive deeper into the practical side of structured concurrency. This more advanced subject will nicely round up our discussion about `CoroutineContext` and `CoroutineScope` before moving on to meatier topics like exception handling.

See you next time.

*Your friend,*

*Max*
