Structured Concurrency

Parent and Children

Structured concurrency means every coroutine has a parent, and coroutines form a tree. Three rules follow:

  1. A parent waits for all its children before it completes.
  2. Cancelling a parent cancels all children.
  3. If a child fails, the failure cancels the parent and its other children (unless a supervisor is used).
viewModelScope
 └─ launch (loadScreen)
     ├─ async (profile)
     ├─ async (feed)
     └─ launch (analytics)

cancel(viewModelScope) → every coroutine below is cancelled. No leaks.

Parent Waits for Children

Example

The parent body ends early, but the parent job completes only after both children.

import kotlinx.coroutines.*

fun main() = runBlocking {
    val parent = launch {
        launch { delay(200); println("Child A done") }
        launch { delay(100); println("Child B done") }
        println("Parent body finished")
    }
    parent.join()
    println("Parent completed: ${parent.isCompleted}")
}

Output

Parent body finished
Child B done
Child A done
Parent completed: true

Cancelling the Parent

Example

Cancelling the parent cancels every child. The child sees a CancellationException.

import kotlinx.coroutines.*

fun main() = runBlocking {
    val parent = launch {
        repeat(2) { i ->
            launch {
                try {
                    delay(1000)
                    println("Child $i finished")  // never printed
                } finally {
                    println("Child $i cancelled")
                }
            }
        }
    }
    delay(100)
    parent.cancelAndJoin()
    println("Parent cancelled: ${parent.isCancelled}")
}

Output

Child 0 cancelled
Child 1 cancelled
Parent cancelled: true

Tip: Structured concurrency is why you rarely leak coroutines in Android: when the user leaves the screen, viewModelScope is cancelled and all network calls started from it stop.