ProgrammingKotlin Software Engineer

How do coroutines work in Kotlin? Describe the mechanism of launching, dispatching, cancellation, and error handling. Provide an example of usage and explain the nuances related to context passing and exceptions.

Pass interviews with Hintsage AI assistant

Answer

Coroutines in Kotlin are lightweight threads with support for pausing and resuming computations without blocking OS threads. They are launched through functions such as launch, async when there is a parent CoroutineScope.

Dispatching occurs via CoroutineDispatcher objects, which define the thread/executor in which the coroutine is executed (Dispatchers.Main, Dispatchers.IO, Dispatchers.Default).

Example:

fun main() = runBlocking { launch(Dispatchers.IO) { val data = getDataFromNetwork() withContext(Dispatchers.Main) { updateUI(data) } } }

Cancellation: Coroutines are canceled by canceling their job/Scope, which throws an internal CancellationException. Coroutines should periodically check the cancellation flag or call suspending functions to terminate correctly.

Error handling: Exceptions in coroutines can "sink" — for example, if you don't handle an error in a child coroutine, it will just finish its execution, and the parent will remain unaware of the issue. There are mechanisms like SupervisorJob and CoroutineExceptionHandler for this purpose.

Nuances of context passing:

  • The context (e.g., Job, Dispatcher, user-defined elements) is inherited when creating new coroutines.
  • You cannot simply pass parameters between scopes if they are closed or canceled.

Trick Question

What will happen if one of the child coroutines inside a parent coroutine fails with an error while others continue to run? Will all children be canceled?

Many mistakenly believe that the error "stays in the child".

Correct answer: If a regular Job (or launch) is used, then all child coroutines are automatically canceled upon any child's error. To prevent child coroutines from being canceled, SupervisorJob or supervisorScope is used:

supervisorScope { launch { error("fail") } launch { println("This code will execute") } }

Examples of real errors due to ignorance of the nuances of the topic


Story

Improper error handling — flow crash: In the project, coroutines were used to load data. Exceptions "sank" within child coroutines, causing the retry/recovery level not to work, and the main screen remained empty without any error messages for the user.


Story

UI freeze due to working with Dispatchers.Main: A young Android developer launched a heavy computation task on Dispatchers.Main — the UI started to "freeze". There was no understanding that any heavy computations should be executed on Dispatchers.Default or Dispatchers.IO.


Story

AbortError when canceling parent scope: One of the developers did not consider that when the parent scope is canceled, all child coroutines are completed with cancellation, and a critically important operation (data saving) was not completed.