skip to content

What is a SupervisorJob in Kotlin coroutines, and how does it differ from a regular Job when one child fails?

level: juniorimportance: must knowfreq 70%

answer

  1. Regular Job: child fails -> parent + all siblings cancelled
  2. SupervisorJob: child fails -> siblings + parent survive
  3. Cancellation still flows DOWN with supervisor
  4. supervisorScope { } = scope backed by SupervisorJob
  5. Does not swallow exceptions; still need a handler

basics

~10 s

A SupervisorJob is a special parent for coroutines. If one child crashes, the others keep running and the parent stays alive. With a regular Job, one crash cancels everything.

solid answer

~40 s

A Job is the handle that controls a coroutine's lifecycle and links parents to children. With a normal Job, failure cancellation propagates both ways: a failing child cancels its parent, which then cancels all sibling children. SupervisorJob() reverses the downward part: a child's failure is NOT propagated upward, so the parent and siblings keep running; cancellation still flows downward (cancelling the parent cancels all children). You install it via CoroutineScope(SupervisorJob() + Dispatchers.Default), or use the supervisorScope { } builder which creates a scope backed by a SupervisorJob for its block. This makes it the basis for resilient scopes where independent tasks must not take each other down — e.g. a UI scope handling several independent requests.

code

kotlin · 3 lines
kotlin
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
scope.launch { throw RuntimeException("boom") } // dies alone
scope.launch { delay(100); println("still alive") } // survives

go deeper

for a junior

Can state that a failing child does not cancel siblings or parent, unlike a regular Job.

for a middle

Adds that cancellation still flows downward and knows supervisorScope as the structured form.

for a senior

Explains that exceptions are still reported and require a handler; distinguishes propagation from handling.

for a principal

Frames it as the building block for resilient scopes and reasons about when downward-only failure isolation is the right contract.

## What a Job is Every coroutine has a `Job` — an object that represents its lifecycle (active, completing, cancelled, completed) and forms a **parent/child tree**. A coroutine launched inside a scope becomes a child of that scope's Job. This tree is what powers **structured concurrency**: parents wait for children, and cancellation flows through the tree. ## Normal Job: failure propagates BOTH ways With a regular `Job`, when a child coroutine **fails** (throws an uncaught exception, not a `CancellationException`): 1. The exception propagates **up** to the parent. 2. The parent cancels itself. 3. The parent then cancels **all its other children** (the failing child's siblings). So one crash takes down the whole scope. This is correct when the tasks are interdependent. ## SupervisorJob: failure does NOT propagate up `SupervisorJob()` is a `Job` whose children **fail independently**. A child's failure is not sent to the parent, so the parent and the siblings survive. Crucially, **cancellation still flows downward**: cancelling the SupervisorJob (or the scope) cancels all its children. ```kotlin import kotlinx.coroutines.* fun main() = runBlocking { val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) val a = scope.launch { delay(50) throw RuntimeException("A failed") } val b = scope.launch { delay(200) println("B finished fine") // still runs } joinAll(a, b) } ``` With a plain `Job()` instead of `SupervisorJob()`, the failure of `a` would cancel `b`. ## How you use it - **`CoroutineScope(SupervisorJob() + dispatcher)`** — a long-lived scope (e.g. a service or ViewModel) where independent jobs must not kill each other. - **`supervisorScope { }`** — a suspending builder that creates a child scope backed by a SupervisorJob for the duration of the block, then waits for all children. This is the structured way to get supervisor behavior inside a function. ## Important caveat: exceptions still need handling A SupervisorJob does not *swallow* exceptions. An unhandled child exception is delivered to a `CoroutineExceptionHandler` (for `launch`) or re-thrown on `await()` (for `async`). If no handler is installed, it reaches the thread's uncaught-exception handler. SupervisorJob only changes **propagation among siblings**, not whether the exception is reported.

  • Does using a SupervisorJob mean child exceptions can be ignored?
    No. It only stops failure from cancelling siblings/parent. The exception is still reported — to a CoroutineExceptionHandler for launch, or on await() for async — so you must still handle it.

A regular Job is a string of fairy lights wired in series — one dead bulb kills the strand; a SupervisorJob wires them in parallel — one bulb dies, the rest stay lit.

saying these in an interview costs you the question

  • Saying SupervisorJob makes exceptions disappear / get swallowed
  • Claiming cancellation no longer flows downward
  • Thinking it changes async's await() rethrow behavior
  • Confusing it with try/catch error handling

context