Understanding coroutineScope vs supervisorScope in Kotlin 2025

Kotlin Coroutines have transformed asynchronous programming, offering developers robust tools to manage concurrency. Among these tools, coroutineScope and supervisorScope stand out for their distinct behavior in handling child coroutines. Let's dive deeper into these two scopes, exploring their differences, use cases, and how to use them effectively.

The Basics

coroutineScope

Think of coroutineScope as a strict parent. If one child coroutine encounters an error, it cancels all the other child coroutines in the same scope. This "all-or-nothing" approach is ideal for operations that require consistency or need to fail completely if any part fails.

  • Key Characteristics:
    • Cancels all child coroutines if one fails.
    • Exceptions from child coroutines are re-thrown.
    • Suitable for operations requiring complete success.

supervisorScope

On the other hand, supervisorScope is like a relaxed parent. If one child coroutine fails, the others are unaffected and continue running. This approach is perfect for tasks where partial success is acceptable.

  • Key Characteristics:
    • Isolates child coroutine failures; others continue unaffected.
    • Exceptions from child coroutines don’t propagate to the parent scope.
Great for independent, concurrent tasks

  • Real-World Examples

    coroutineScope: A User Profile Loader

    Imagine loading a user profile that requires fetching:

    • User data
    • User posts
    • Friend list

    If any of these operations fail, the entire profile load fails. This ensures the data presented to the user is either fully loaded or not shown at all.

    supervisorScope: A Dashboard

    Consider a dashboard with widgets like:

    • Weather updates
    • News feed
    • Stock quotes

    If the weather API is down, the news feed and stock quotes should still load. supervisorScope ensures that other widgets remain functional despite one failure.


    Code Comparison

    Here’s how the two scopes handle exceptions differently:


    fun main() = runBlocking { try { coroutineScope { launch { throw Exception("Failure in coroutineScope") } launch { println("This won't execute in coroutineScope.") } } } catch (e: Exception) { println("Exception caught: $e") } supervisorScope { launch { throw Exception("Failure in supervisorScope") } launch { println("This continues in supervisorScope.") } } }

    Output:

    • For coroutineScope: The second child coroutine is canceled due to the failure in the first one.
    • For supervisorScope: The second child coroutine continues despite the failure.

Quick Tips

  1. Use coroutineScope for dependent operations.
  2. Use supervisorScope for independent operations.
  3. Favor coroutineScope when you need data consistency.
  4. Opt for supervisorScope to provide a better user experience with partial data.