Job vs SupervisorJob

Coroutines Senior Kotlin 2.0+ API 24+ Reviewed By Best Latest Apps

Predict the output

Child B throws. Which children finish?

val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default + handler)
scope.launch { delay(500); log("A done") }
scope.launch { delay(100); throw IllegalStateException("B failed") }
scope.launch { delay(500); log("C done") }
Options
Hint 1 of 2

What does a SupervisorJob do when a child fails?

Hint 2 of 2

Where do uncaught launch exceptions go?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A A and C finish; B's error goes to the handler

Correct. A supervisor doesn't cancel siblings, and the handler receives B's uncaught exception.

Why not the others?
  • B Nothing finishes; the whole scope is cancelled. That would be a regular Job().
  • C A and C finish and the error disappears. The error is not swallowed; it goes to the CoroutineExceptionHandler.
  • D Only A finishes because C started after B. All three start right away; order doesn't matter here.
Indented job tree: a scope with children A, B and C. B fails; compare a regular Job with a SupervisorJob.
Text version

With Job (child B fails)

  1. B throws
  2. B's failure cancels the scope's Job
  3. A and C are cancelled
  4. The handler gets B's exception
  • scope: ⊘ cancelled
  • child A: ⊘ cancelled
  • child B: ✕ failed
  • child C: ⊘ cancelled

With SupervisorJob (child B fails)

  1. B throws
  2. The supervisor keeps the scope alive
  3. The handler gets B's exception
  4. A and C complete
  • scope: ● active
  • child A: ✓ completed
  • child B: ✕ failed
  • child C: ✓ completed

30-second answer

With a regular Job, a child that fails cancels its parent, which cancels every other child, and the exception goes up the tree. With a SupervisorJob, a child's failure stays with that child: siblings keep running and the parent is not cancelled. The failing child's exception still has to be handled somewhere.

Practise saying it · 40 s. No recording, just a timer

Explain Job vs SupervisorJob.

Say your answer out loud, then check which of these points you covered:

  • A regular Job cancels siblings on failure
  • SupervisorJob keeps siblings running
  • It doesn't catch exceptions
  • Only direct children are supervised

Deep dive

Under a supervisor, an uncaught exception in a launch child goes to the CoroutineExceptionHandler, or the thread's default handler (a crash on Android).

SupervisorJob only changes its direct children. A grandchild that fails still cancels its own parent and siblings.

launch(SupervisorJob()) { } is a classic mistake: the new SupervisorJob becomes the parent, and the scope loses control of the child.

viewModelScope uses a SupervisorJob, so one failing load doesn't kill the others, but it will still crash the app if nothing catches it.

The handler used above Compile-checked · Kotlin 2.4.21
val handler = CoroutineExceptionHandler { _, e ->
    log("Uncaught: ${e.message}")
}

Common mistakes

  • Passing SupervisorJob() as an argument to launch and expecting supervision.
  • Believing SupervisorJob catches exceptions.
  • Forgetting that grandchildren are not supervised.

Senior take & follow-ups

Use the tree. Say that supervision is about cancellation, not about catching, and mention the launch(SupervisorJob()) trap.

Follow-up questions

Think of your answer first, then open the hint.

  1. Where does an uncaught exception in viewModelScope.launch go?

    Show hint

    To the default handler, which crashes the app, unless you catch it.

  2. How do you supervise children inside a suspend function?

    Show hint

    Use supervisorScope { }.

Suggest a fix if something here is wrong or out of date.