What is SupervisorScope?
Kotlin
What is SupervisorScope?
Akshay Nandwana
January 29, 2025
5 min read
33 views

Kotlin Coroutines provides a structured concurrency model to handle async tasks efficiently. Among its key components, supervisorScope is essential when dealing with child coroutines that should not affect their siblings in case of failures.

In this blog, we will understand supervisorScope with detailed examples, internal workings, edge cases, best practices, and a real-world analogy inspired by Rajasthan's traditional systems.


Join our upcoming classes
Our Courses


Real-World Analogy: Rajasthan's Water Management System

Imagine a stepwell (baori) in Rajasthan, where multiple water storage sections exist. If one section collapses, the others continue functioning without affecting the whole system.

Similarly, supervisorScope ensures that if one coroutine fails, the others continue executing independently.

Another analogy is Rajasthan's district-level irrigation management—each district has its own water reserves, and failure in one area doesn’t lead to complete system collapse, unlike a centralized supply.

Understanding Coroutine Scope vs. Supervisor Scope

Before diving into supervisorScope, let's first understand the difference between CoroutineScope and SupervisorScope.

CoroutineScope

In a normal coroutine scope, if one child coroutine fails, it cancels the entire scope, affecting all other coroutines inside it.

kotlin
import kotlinx.coroutines.*

fun main() {
    runBlocking {
     	checkScope()
    }
}

suspend fun checkScope() = coroutineScope {
    launch {
        delay(1000)
        println("Task 1 Completed")
    }
    launch {
        delay(500)
        throw RuntimeException("Task 2 Failed")
    }
}

Output:

javascript
Exception in thread "main" java.lang.RuntimeException: Task 2 Failed

Here, Task 1 is also canceled because Task 2 failed, demonstrating that all children are affected.

Introducing supervisorScope

With supervisorScope, a failing child does not affect its siblings. This ensures independent execution of tasks within the same parent scope.

kotlin
import kotlinx.coroutines.*

fun main() {
    runBlocking {
     	checkScope()
    }
}

suspend fun checkScope() = supervisorScope {
    launch {
        delay(1000)
        println("Task 1 Completed")
    }
    launch {
        delay(500)
        throw RuntimeException("Task 2 Failed")
    }
}

Output:

javascript
Exception in thread "main" java.lang.RuntimeException: Task 2 Failed
Task 1 Completed

Now, Task 1 successfully completes execution even though Task 2 fails.

Internal Working of supervisorScope

Internally, supervisorScope creates a SupervisorJob, which ensures that the failure of one child coroutine does not cancel the entire scope.

kotlin
suspend fun supervisorScope(block: suspend CoroutineScope.() -> Unit) {
    coroutineScope {
        val supervisorJob = SupervisorJob()
        val newScope = this + supervisorJob
        newScope.block()
    }
}
  • SupervisorJob allows child coroutines to work independently.

  • Unlike CoroutineScope, it does not propagate failure to siblings.

  • It still ensures structured concurrency within the block.

Best Practices and Edge Cases

When supervisorScope Might Not Be Ideal

  • If you need complete failure rollback, supervisorScope is not suitable.

  • If the failure of one task should halt everything (e.g., a transaction system), use a regular coroutine scope.

Common Mistakes to Avoid

  • Forgetting to handle failures: supervisorScope doesn’t prevent failures; you must handle them inside coroutines using try-catch.

  • Using supervisorScope at the top level: It’s better suited for handling specific independent child tasks rather than being applied to an entire application scope.

Real-World Example: Managing Rajasthan Tourism Bookings

Imagine an online tourism system for Rajasthan:

  • A user books a Desert Safari and a City Palace Tour together.

  • If the Desert Safari fails due to weather conditions, the City Palace Tour should still proceed.

Using supervisorScope, we can model this system:

kotlin
import kotlinx.coroutines.*

fun main() {
    runBlocking {
     	rajasthanTrip()
    }
}

suspend fun rajasthanTrip() = supervisorScope {
    	launch {
            try {
                delay(500)
                throw Exception("Desert Safari Booking Failed")
            } catch (e: Exception) {
                println(e.message)
            }
        }
        
        launch {
            delay(1000)
            println("City Palace Tour Confirmed")
        }
}

Output:

javascript
Desert Safari Booking Failed
City Palace Tour Confirmed

Even though the safari booking fails, the palace tour continues as planned.

When to Use supervisorScope

Use supervisorScope when:

  • You have multiple independent coroutines where one failure should not affect others.

  • You are handling UI operations where a failed network call should not crash the entire screen.

  • You need isolated error handling without canceling the entire scope.

Exception Handling in supervisorScope

While supervisorScope prevents failures from propagating to sibling coroutines, it does not automatically handle exceptions. You still need to catch them properly within each coroutine.

Handling Exceptions with try-catch

kotlin
import kotlinx.coroutines.*

fun main() {
    runBlocking {
     	rajasthanTrip()
    }
}

suspend fun rajasthanTrip() = supervisorScope {
    	launch {
            try {
                delay(500)
                throw Exception("Task 1 Failed")
            } catch (e: Exception) {
                println("Caught Exception in Task 1: ${e.message}")
            }
        }
        
        launch {
            delay(1000)
            println("Task 2 Completed Successfully")
        }
}

Output:

javascript
Caught Exception in Task 1: Task 1 Failed
Task 2 Completed Successfully

Here, Task 2 continues running because the failure in Task 1 is handled gracefully.

Handling Exceptions with CoroutineExceptionHandler

Instead of using try-catch inside each coroutine, you can use a CoroutineExceptionHandler to catch all uncaught exceptions.

kotlin
import kotlinx.coroutines.*

fun main() {
    runBlocking {
         val exceptionHandler = CoroutineExceptionHandler { _, exception ->
        println("Caught Exception: ${exception.message}")
    }
         
     	rajasthanTrip(exceptionHandler)
    }
}

suspend fun rajasthanTrip(exceptionHandler: CoroutineExceptionHandler) = supervisorScope {
    	launch(exceptionHandler) {
            delay(500)
            throw Exception("Desert Safari Booking Failed")
        }
        
        launch {
            delay(1000)
            println("City Palace Tour Confirmed")
        }
}

Output:

javascript
Caught Exception: Desert Safari Booking Failed
City Palace Tour Confirmed

Combining supervisorScope with Retry Mechanism

For transient failures (e.g., network issues), you can retry the failing coroutine.

kotlin
import kotlinx.coroutines.*
import kotlin.random.Random

fun main() {
    runBlocking { 
     	rajasthanTrip()
    }
}

suspend fun retryTask(): String {
    repeat(3) { attempt ->
        try {
            delay(500)
            if (Random.nextBoolean()) throw Exception("Random Failure")
            return "Success on attempt ${attempt + 1}"
        } catch (e: Exception) {
            println("Attempt ${attempt + 1} failed: ${e.message}")
        }
    }
    return "Failed after retries"
}

suspend fun rajasthanTrip() = supervisorScope {
    	launch {
            val result = retryTask()
            println(result)
        }

        launch {
            delay(1000)
            println("Other task running normally")
        }
}

Output:

javascript
Attempt 1 failed: Random Failure
Attempt 2 failed: Random Failure
Other task running normally
Attempt 3 failed: Random Failure
Failed after retries

Conclusion

supervisorScope is a powerful feature in Kotlin Coroutines that ensures failure resilience. By using it, we can create robust applications where independent tasks execute without unnecessary cancellations.

Just like Rajasthan's ancient water systems ensured survival despite failures in certain sections, supervisorScope keeps coroutines running even when some fail.

Key Takeaways:

  • supervisorScope prevents failures from propagating to sibling coroutines.

  • It internally uses SupervisorJob for structured concurrency.

  • Best suited for independent tasks where failures should be isolated.

  • Always handle exceptions properly within supervisorScope.

Akshay Nandwana
Founder AndroidEngineers

You can connect with me on:


Book 1:1 Session here
Click Here

Join our upcoming classes
Our Courses

Share This Article
Stay Updated

Get the latest Android development articles delivered to your inbox.