Blog Infos
Author
Published
Topics
, , , ,
Published

Structured concurrency in Kotlin relies heavily on CoroutineScope and SupervisorScope to manage coroutine lifecycles. Let’s break down how they work internally and when to use each.

1. CoroutineScope: The Parent-Child Relationship
How It Works:
  • Creates a scope where child coroutines are bound to the parent
  • If any child coroutine fails, it cancels the parent and all siblings
  • Uses a regular Job() internally
Simple Example:

 

fun main() = runBlocking {
    val scope = CoroutineScope(Job())
    
    scope.launch {
        println("Child 1 started") // (1)
        delay(100)
        throw RuntimeException("Error in Child 1!") // (3)
    }
    
    scope.launch {
        println("Child 2 started") // (2)
        delay(200)
        println("Child 2 completed") // (X) Never reaches here!
    }
    
    delay(300)
    println("Main ends")
}

 

Output:

 

Child 1 started
Child 2 started
Exception in thread "main" RuntimeException: Error in Child 1!

 

What Happened?

  • Child 1 fails → Cancels the entire scope → Child 2 gets cancelled too.
2. SupervisorScope: Isolated Failure Handling
How It Works:
  • Creates a scope where child coroutines don’t affect siblings if they fail
  • Uses a SupervisorJob() internally which suppresses exception propagation
  • You must handle exceptions manually in each child
Simple Example:

 

fun main() = runBlocking {
    val scope = CoroutineScope(SupervisorJob()) // ← Only change!
    
    scope.launch {
        println("Child 1 started") // (1)
        delay(100)
        throw RuntimeException("Error in Child 1!") // (3)
    }
    
    scope.launch {
        println("Child 2 started") // (2)
        delay(200)
        println("Child 2 completed") // (4) This now executes!
    }
    
    delay(300)
    println("Main ends") // (5)
}

 

Output:

Child 1 started
Child 2 started
Exception... (logged but doesn't crash)
Child 2 completed
Main ends
What Changed?
  • Child 1 fails → But Child 2 continues execution normally.
Key Differences Table

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

,

Meta-programming with K2 compiler plugins

Let’s see what’s possible with plugins using the new K2 compiler, FIR. This live demo session will go through possible use cases that reduce boilerplate code and make your code safer.
Watch Video

Meta-programming with K2 compiler plugins

Tadeas Kriz
Senior Kotlin Developer
Touchlab

Meta-programming with K2 compiler plugins

Tadeas Kriz
Senior Kotlin Develo ...
Touchlab

Meta-programming with K2 compiler plugins

Tadeas Kriz
Senior Kotlin Developer
Touchlab

Jobs

When to Use Which?
Use CoroutineScope when:

 

// Transfer money (if either fails, both should roll back)
CoroutineScope(Job()).launch {
    val withdraw = async { bank.withdraw(amount) }
    val deposit = async { receiver.deposit(amount) }
    withdraw.await()
    deposit.await() // If this fails, withdraw is cancelled
}

 

Use SupervisorScope when:

// Fetch user profile + recommendations (failure in one shouldn't cancel the other)
SupervisorScope().launch {
    launch { println("Loading profile: ${fetchProfile()}") }
    launch { println("Loading ads: ${fetchAds()}") } // If this fails, profile still loads
}
Pro Tip: supervisorScope Builder

For temporary supervisor behavior inside a coroutine:

fun main() = runBlocking {
    supervisorScope {
        launch {
            println("Task 1 running")
            throw Exception("Oops!") // (1)
        }
        launch {
            delay(100)
            println("Task 2 still runs!") // (2)
        }
    }
    println("Main completes") // (3)
}

Output:

Task 1 running
Task 2 still runs!
Exception... (logged)
Main completes

This creates an isolated “bubble” where failures are contained.

Final Thoughts
  • CoroutineScope = Strict parent (kids misbehave → everyone goes home)
  • SupervisorScope = Chill parent (one kid messes up → others keep playing)

Choose based on whether failures should be isolated or atomic. For beginners: Start with CoroutineScope and switch to SupervisorScope only when you need independent failure handling.

This article was previously published on proandroiddev.com.

Menu