Coroutine Exceptions & Error Handling Quiz

KOTLIN › Coroutines

Why does wrapping scope.launch { } in a try/catch never catch the coroutine is exception?

Answer: launch returns once the body is scheduled, so the try block has already exited

The builder schedules the body and returns. By the time the throw executes the try has exited, so the exception fails the Job and travels up the job hierarchy instead.

Where must a CoroutineExceptionHandler be installed to fire?

Answer: On a coroutine that is a root of its scope, or on the scope context itself

Only a root coroutine consults its handler. A nested child reports its failure upward instead, so a handler in its context is ignored and the app still crashes.

What does this print?

Answer: Nothing, and the app crashes: the inner coroutine is not a root

The inner launch is a child, so its failure propagates to the parent rather than being handled locally. Moving the handler to the outer launch, or to the scope, makes it fire.

Which statement about CancellationException is correct?

Answer: It is never delivered to a CoroutineExceptionHandler and never fails a parent

It signals intentional shutdown rather than failure, so it is excluded from propagation entirely. It is an ordinary Exception subclass though, which is exactly why a broad catch swallows it by accident.

Inside coroutineScope, one of three async calls throws. What does the caller see?

Answer: The original exception rethrown, with the other two coroutines cancelled

The failure cancels the scope and therefore the siblings, then the scope rethrows. Making one call optional means isolating it, for example with runCatching inside its own async.

Two sibling coroutines throw at nearly the same moment. How does the second exception reach you?

Answer: Attached to the first as a suppressed exception

The first failure to reach the parent is thrown and later ones are recorded in its suppressed array, which is why logging stackTraceToString() rather than message matters.

What is wrong with this retry helper?

Answer: It retries on cancellation, so leaving the screen triggers more attempts

The broad catch matches CancellationException, so cancelling the caller looks like a retryable failure. A CancellationException clause that rethrows must come first.

Under supervisorScope, why does a handler on a direct child fire when the same code under coroutineScope ignores it?

Answer: Because a supervisor refuses the failure, making each direct child a root for handling

Handlers apply to coroutines whose failure has nowhere further to go. Declining to absorb the failure is exactly what makes a supervised child a root.

How far does supervisorScope isolation extend?

Answer: To direct children only: a grandchild failure still cancels its own sibling group

A grandchild reports to its own ordinary parent, so that inner group cancels together. The supervisor only stops the failure spreading to the outer siblings.

What is the danger of runCatching { api.fetch() } inside a coroutine?

Answer: It catches Throwable, including CancellationException, breaking cancellation

Swallowing the cancellation signal means a cancelled coroutine keeps going as though the call merely failed. A typed wrapper that rethrows CancellationException first avoids it.

A root async throws and nothing ever calls await(). What happens?

Answer: The exception is stored in the Deferred and silently lost

Handlers are ignored for async, and with no parent watching, await() is the only surfacing path. Never calling it loses the failure entirely.

Which failure belongs in a try/catch rather than a CoroutineExceptionHandler?

Answer: An IOException from a network call the UI should show a retry button for

A handler is a crash-reporting tool of last resort. A failure with a meaningful response belongs where the code that knows the response can see it.

What is TimeoutCancellationException and how does it behave?

Answer: A CancellationException subclass, so it cancels only its block and never fails the scope

Because it is a CancellationException, it obeys every rule about cancellation not being a failure, which is why withTimeout scopes its effect to its own block.

Why does catching an exception inside one child not stop the parent scope from being cancelled?

Answer: Because catching handles your own failure and has no say over a sibling failing

Cancellation of the scope is driven by whichever child actually failed. Handling your own exception keeps you alive but does nothing about a sibling that did not.

What is the recommended layering for turning failures into UI state?

Answer: Repositories return Result or a sealed type; ViewModels map that to state

Making failure part of the return type means a caller cannot forget it, and the ViewModel needs no try/catch. A handler cannot render an error screen because it has no idea which screen was loading.

How does exception delivery differ between launch and async?

Answer: launch propagates to its scope immediately; async holds it for await()

launch treats an uncaught exception as a failure and propagates it up the scope right away, while async stores it in the Deferred so it surfaces when you call await().

Back to Coroutine Exceptions & Error Handling