What is SupervisorScope?

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.

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:

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.

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:

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.

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:

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:

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

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:

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.

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:

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.

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:

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

Love from our past students

Excellent list of questions really helped me to coverup all the topics before interview.

Saiteja Janjirala

10th Oct, 2024

I had an exceptional experience with the 1:1 mentorship session. Akshay was incredibly friendly and provided invaluable guidance on focusing on long-term goals. They also gave great interview tips, including a thorough resume review. Additionally, the discussion on Data Structures and Algorithms (DSA) was insightful and practical. Highly recommended for anyone looking to advance their career!

Nayab khan

11th Sep, 2024

Cleared my major points for what I am missing in the resume and also suggested what I can work on for further growth in the career.

Ketan Chaurasiya

7th Aug, 2024

What impressed me most was his personalized approach and practical tips that I could immediately apply. Akshay’s guidance not only improved my technical skills but also boosted my confidence in navigating my career path. His insights and encouragement have been a game-changer for me. I highly recommend Akshay’s mentorship to anyone looking to advance their Android development career.

Hardik Kubavat

5th Aug, 2024

2025© Made with   by Android Engineers.