Kotlin Coroutine Scope

A coroutine scope defines the lifetime of coroutines. Every coroutine runs inside a scope. When the scope is cancelled or completes, all coroutines inside it are cancelled automatically. Scopes prevent coroutines from running indefinitely or leaking resources.

Why Scope Matters


Without scope:
  launch { ... }   // runs forever? who owns it? when does it stop?

With scope:
  scope.launch { ... }   // tied to scope's lifetime
  scope.cancel()         // all coroutines inside are cancelled

This is "structured concurrency" — no orphaned coroutines.

CoroutineScope

import kotlinx.coroutines.*

fun main() = runBlocking {
    // coroutineScope waits for all child coroutines to finish
    coroutineScope {
        launch { delay(500); println("Task A") }
        launch { delay(300); println("Task B") }
        println("Scope started")
    }
    println("Scope finished — both tasks are done")
}

Output:

Scope started
Task B
Task A
Scope finished — both tasks are done

coroutineScope vs runBlocking


runBlocking:
  Blocks the thread until all coroutines inside complete.
  Used to bridge regular code and coroutine code.
  OK in main() and tests.

coroutineScope:
  Suspends (not blocks) until all children complete.
  Used inside suspend functions and other coroutines.
  Does NOT block any thread.

Custom Scope with CoroutineScope

import kotlinx.coroutines.*

class DataManager {
    private val scope = CoroutineScope(Dispatchers.Default)

    fun startLoading() {
        scope.launch {
            delay(1000)
            println("Data loaded on: ${Thread.currentThread().name}")
        }
    }

    fun cleanup() {
        scope.cancel()   // cancels all running coroutines
        println("Scope cancelled")
    }
}

fun main() {
    val manager = DataManager()
    manager.startLoading()
    Thread.sleep(1500)   // wait for loading
    manager.cleanup()
}

Dispatchers


Dispatcher           │ Thread Pool          │ Best for
─────────────────────┼──────────────────────┼──────────────────────
Dispatchers.Default  │ CPU cores            │ heavy computation
Dispatchers.IO       │ up to 64 threads     │ file/network/database
Dispatchers.Main     │ UI thread (Android)  │ UI updates
Dispatchers.Unconfined│ caller's thread     │ testing/special cases
import kotlinx.coroutines.*

fun main() = runBlocking {
    launch(Dispatchers.Default) {
        println("Default: ${Thread.currentThread().name}")
    }
    launch(Dispatchers.IO) {
        println("IO: ${Thread.currentThread().name}")
    }
    delay(500)
}

withContext — Switch Dispatcher

Use withContext to switch dispatchers inside a suspend function without creating a new coroutine:

suspend fun loadFile(path: String): String = withContext(Dispatchers.IO) {
    println("Reading on: ${Thread.currentThread().name}")
    delay(400)  // simulate file read
    "File content from $path"
}

suspend fun processData(data: String): String = withContext(Dispatchers.Default) {
    println("Processing on: ${Thread.currentThread().name}")
    data.uppercase()
}

fun main() = runBlocking {
    val raw = loadFile("/data/report.txt")
    val result = processData(raw)
    println(result)
}

Scope Hierarchy


runBlocking (root scope)
  └── coroutineScope (child scope)
        ├── launch → child coroutine A
        └── launch → child coroutine B

If coroutineScope is cancelled:
  → A is cancelled
  → B is cancelled
  → parent (runBlocking) continues

If child A throws an exception:
  → B is cancelled
  → coroutineScope propagates the exception to parent

Practical Example: Structured Data Fetcher

import kotlinx.coroutines.*

suspend fun fetchOrders(): List = withContext(Dispatchers.IO) {
    delay(500)
    listOf("Order #1001", "Order #1002", "Order #1003")
}

suspend fun fetchInventory(): Map = withContext(Dispatchers.IO) {
    delay(400)
    mapOf("Laptop" to 15, "Mouse" to 50, "Keyboard" to 30)
}

fun main() = runBlocking {
    coroutineScope {
        val ordersDeferred    = async { fetchOrders() }
        val inventoryDeferred = async { fetchInventory() }

        val orders    = ordersDeferred.await()
        val inventory = inventoryDeferred.await()

        println("Orders: $orders")
        println("Inventory: $inventory")
    }
    println("Done")
}

Leave a Comment

Your email address will not be published. Required fields are marked *