Skip to content

Instantly share code, notes, and snippets.

@vitalikas
Last active December 5, 2025 08:54
Show Gist options
  • Select an option

  • Save vitalikas/8080de296e15d8be9430ffe95505a30f to your computer and use it in GitHub Desktop.

Select an option

Save vitalikas/8080de296e15d8be9430ffe95505a30f to your computer and use it in GitHub Desktop.
SupervisorJob vs Regular Job - Complete Demonstration (FAST VERSION)
/**
* SupervisorJob vs Regular Job - Complete Demonstration (FAST VERSION)
*
* This version uses shorter delays (100ms instead of 3-5 seconds) for online playgrounds
*
* Run in Kotlin Playground: https://play.kotlinlang.org/
*/
import kotlinx.coroutines.*
// Helper: Coroutine Exception Handler
val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception ->
println(" 🛡️ CoroutineExceptionHandler caught: ${exception.message}")
}
// Helper: Simulate a task that fails (FAST - 100ms instead of 3s)
suspend fun checkNotifications() {
println(" ⏰ Checking notifications...")
delay(100) // Short delay for demo
println(" 💥 Notification check - THROWING EXCEPTION!")
throw Exception("Notification check failed")
}
// Helper: Simulate a task that succeeds (FAST - 200ms instead of 5s)
suspend fun updateDailyNews() {
println(" 📰 Updating daily news...")
delay(200) // Short delay for demo
println(" ✅ Daily news updated successfully!")
}
fun main() = runBlocking {
println("=".repeat(70))
println("SupervisorJob vs Regular Job - Complete Demonstration")
println("=".repeat(70))
println()
println(
"""
🎯 THE FUNDAMENTAL RULE: "Job is NOT inherited by children"
What this means:
- When you create CoroutineScope(SupervisorJob), it has SupervisorJob-A
- When you call scope.launch(), it creates a NEW Job (Job-B)
- Job-B is ALWAYS a regular Job (not SupervisorJob), with Job-A as its parent
- Job-A is used as a PARENT reference, not copied/inherited
Why this matters:
- Only DIRECT children of SupervisorJob get supervisor isolation
- Grandchildren are under a regular Job → normal cancellation behavior
""".trimIndent())
println()
println("=".repeat(70))
println()
// ❌ Scenario 1: Without SupervisorJob (regular Job)
println("--- Scenario 1: Regular Job (WRONG) ---")
println("Structure: scope with Job() → launch { task1() }; launch { task2() }")
println()
val regularScope = CoroutineScope(Dispatchers.Default + Job())
regularScope.launch(coroutineExceptionHandler) {
checkNotifications() // Throws exception after 100ms
}
regularScope.launch {
updateDailyNews() // This will be CANCELLED when first coroutine fails!
}
delay(300) // Wait for tasks
println("Regular Job scope status: ${if (regularScope.isActive) "ACTIVE" else "CANCELLED"}")
println("Result: ❌ updateDailyNews was CANCELLED")
println()
println("=".repeat(70))
println()
// ❌ Scenario 2: SupervisorJob but WRONG structure (sequential in same coroutine)
println("--- Scenario 2: Sequential in Same Coroutine (WRONG) ---")
println("Structure: scope.launch { task1(); task2() }")
println()
val scopeSequential = CoroutineScope(Dispatchers.Default + SupervisorJob())
scopeSequential.launch(coroutineExceptionHandler) {
checkNotifications() // Throws exception after 100ms
updateDailyNews() // ❌ NEVER RUNS - not reached because exception thrown first
}
delay(300) // Wait for tasks
println("Sequential scope status: ${if (scopeSequential.isActive) "ACTIVE" else "CANCELLED"}")
println("Result: ❌ updateDailyNews never started (sequential execution)")
println()
println("=".repeat(70))
println()
// ❌ Scenario 3: SupervisorJob with NESTED launches (WRONG - Grandchildren problem!)
println("--- Scenario 3: Nested Launches - Grandchildren Problem (WRONG) ---")
println("Structure: scope.launch { launch { task1() }; launch { task2() } }")
println()
println(
"""
Why this fails:
1. scope.launch() creates Job-B (parent: SupervisorJob-A)
2. Inner launch() calls create Job-C and Job-D (parent: Job-B)
3. Job-C and Job-D are GRANDCHILDREN of SupervisorJob-A
4. When Job-C fails → Job-B fails → Job-D cancelled
Visual:
SupervisorJob-A
└─ Job-B (regular Job)
├─ Job-C: checkNotifications() ← fails
└─ Job-D: updateDailyNews() ← CANCELLED
""".trimIndent())
println()
val scopeNested = CoroutineScope(Dispatchers.Default + SupervisorJob())
scopeNested.launch(coroutineExceptionHandler) {
launch { checkNotifications() } // Inner Child 1 - throws exception
launch { updateDailyNews() } // Inner Child 2 - GETS CANCELLED
}
delay(300) // Wait for tasks
println("Nested scope status: ${if (scopeNested.isActive) "ACTIVE" else "CANCELLED"}")
println("Result: ❌ updateDailyNews was CANCELLED (grandchildren don't get benefits)")
println()
println("=".repeat(70))
println()
// ✅ Scenario 4: Direct children of SupervisorJob (BEST PRACTICE)
println("--- Scenario 4: Direct Children of SupervisorJob (BEST PRACTICE) ✅ ---")
println("Structure: scope.launch { task1() }; scope.launch { task2() }")
println()
println(
"""
Why this works:
1. Both launch() calls are DIRECT children of SupervisorJob-A
2. When one child fails, SupervisorJob isolates the failure
3. Other children continue running independently
Visual:
SupervisorJob-A
├─ Job-C: checkNotifications() ← fails (isolated)
└─ Job-B: updateDailyNews() ← CONTINUES ✅
""".trimIndent())
println()
val scopeDirectChildren = CoroutineScope(Dispatchers.Default + SupervisorJob())
scopeDirectChildren.launch(coroutineExceptionHandler) {
checkNotifications() // Direct child of SupervisorJob-A
}
scopeDirectChildren.launch {
updateDailyNews() // Direct child of SupervisorJob-A
}
delay(300) // Wait for tasks
println("Direct children scope status: ${if (scopeDirectChildren.isActive) "ACTIVE" else "CANCELLED"}")
println("Result: ✅ updateDailyNews COMPLETED! SupervisorJob protected its direct children!")
println()
println("=".repeat(70))
println()
// ✅ Scenario 5: SupervisorJob with supervisorScope (CORRECT but more complex)
println("--- Scenario 5: Using supervisorScope (CORRECT but more complex) ---")
println("Structure: scope.launch { supervisorScope { launch { task1() }; launch { task2() } } }")
println()
println(
"""
How supervisorScope fixes nesting:
- supervisorScope creates a NEW SupervisorJob for its children
- Inner launches become direct children of this new SupervisorJob
- Failure isolation works again!
""".trimIndent())
println()
val scopeSupervisorScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
scopeSupervisorScope.launch {
supervisorScope {
launch(coroutineExceptionHandler) { checkNotifications() }
launch { updateDailyNews() }
}
}
delay(300) // Wait for tasks
println("supervisorScope status: ${if (scopeSupervisorScope.isActive) "ACTIVE" else "CANCELLED"}")
println("Result: ✅ updateDailyNews COMPLETED! supervisorScope isolated the failure!")
println()
println("=".repeat(70))
println()
// Final Summary
println("📝 FINAL SUMMARY:")
println()
println("| Scenario | Description | Result |")
println("|----------|--------------------------------|-------------------------------------|")
println("| 1 | Regular Job | ❌ Sibling cancelled |")
println("| 2 | Sequential in same coroutine | ❌ Never reached |")
println("| 3 | Nested launches (grandchildren)| ❌ Grandchildren cancelled |")
println("| 4 | Direct children | ✅ PROTECTED by SupervisorJob BEST! |")
println("| 5 | supervisorScope | ✅ Works but more complex |")
println()
println("=".repeat(70))
println()
println("🎯 KEY TAKEAWAY:")
println("Only DIRECT children of SupervisorJob get supervisor benefits!")
println()
println("💡 BEST PRACTICE (Scenario 4):")
println(
"""
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
scope.launch(exceptionHandler) {
checkNotifications() // Direct child - can fail independently
}
scope.launch {
updateDailyNews() // Direct child - continues even if other fails
}
""".trimIndent())
println()
println("=".repeat(70))
println("Demonstration complete!")
println("=".repeat(70))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment