skip to content

Context & Dispatchers

CoroutineContext is a typed set of elements — the Job, the dispatcher, a name — and dispatchers are the element that decides which threads run your code. Context questions are where thread-pool and confinement discussions land.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

explore

questions

15

What is a CoroutineContext in Kotlin coroutines, and what kind of data does it hold?

level: juniorimportance: must knowfreq 70%

answer

  1. Indexed set of elements keyed by Key
  2. Job + Dispatcher + CoroutineName + ExceptionHandler
  3. Immutable, combined with +
  4. Read via context[Key]
  5. coroutineContext property inside coroutines

basics

~20 s

It is a small set of settings carried by every coroutine. It bundles things like which thread pool runs the coroutine, its Job (lifecycle), and an optional name, so each coroutine knows how it should run.

solid answer

~30 s

CoroutineContext is an immutable, indexed collection of Element objects, each identified by a unique Key. Every coroutine runs with one. Typical elements are the Job (lifecycle/cancellation), a CoroutineDispatcher (which thread it runs on), CoroutineName (for debugging), and CoroutineExceptionHandler. You build a context by combining elements with the + operator (e.g. Dispatchers.IO + CoroutineName("loader")) and pass it to launch/async or withContext. You look up an element by its key with context[Key]. It behaves a bit like an immutable map keyed by element type, but each element is also a context itself. Children inherit the parent context and can override individual elements.

code

kotlin · 10 lines
kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    val context = Dispatchers.Default + CoroutineName("worker")
    launch(context) {
        println(coroutineContext[CoroutineName])      // CoroutineName(worker)
        println(coroutineContext[Job])                // a Job instance
        println(coroutineContext[ContinuationInterceptor]) // the dispatcher
    }.join()
}

go deeper

for a junior

Knows a context holds the dispatcher, Job, and name, is immutable, and is combined with +.

for a middle

Can read elements via context[Key], distinguish context from scope, and explain inheritance to children.

for a senior

Explains the Element/Key model, that an element is itself a one-element context, and immutability's safety benefits.

for a principal

Reasons about context as a foldable typed set, designing custom context elements and library APIs around it.

## What CoroutineContext is A `CoroutineContext` is an **immutable, indexed set of elements** that travels with every coroutine. Think of it as a tiny, type-safe map describing *how* a coroutine runs. Every `launch`, `async`, `runBlocking`, or `withContext` operates inside some context. ## The pieces - **Element**: a single entry in the context (e.g. a dispatcher). Each element implements `CoroutineContext.Element`, which itself extends `CoroutineContext` (a one-element context). - **Key**: a unique identifier of type `CoroutineContext.Key<E>` used to find an element. Each element exposes `key`. Looking up uses `context[SomeKey]`. ## Common elements - **`Job`** — the coroutine's lifecycle handle: tracks active/completing/cancelled state and parent-child relationships. - **`CoroutineDispatcher`** — decides which thread/pool runs the coroutine (`Dispatchers.Default`, `Dispatchers.IO`, `Dispatchers.Main`). - **`CoroutineName`** — a label shown in debugging/logs. - **`CoroutineExceptionHandler`** — last-resort handler for uncaught exceptions in root coroutines. ## Building and reading a context ```kotlin import kotlinx.coroutines.* val ctx = Dispatchers.IO + CoroutineName("loader") fun main() = runBlocking { launch(ctx) { // read an element by its key val name = coroutineContext[CoroutineName] // CoroutineName(loader) println(name?.name) }.join() } ``` `coroutineContext` is an implicit property available inside any `suspend` function/coroutine body. You combine elements with `+`; you read them by indexing with a key (here the companion object `CoroutineName` *is* the key). ## Key idea The context is **immutable**: combining with `+` returns a new context; it never mutates an existing one. This makes it safe to share and to inherit across parent/child coroutines.

  • Is CoroutineContext mutable?
    No. It is immutable; the + operator returns a new combined context rather than modifying an existing one.
  • How do you access the current context inside a suspend function?
    Use the implicit coroutineContext property, available in any coroutine body or suspend function.

Like a backpack each coroutine carries: it holds a few labeled items (which thread, its lifecycle Job, a name) that tell it how to run.

saying these in an interview costs you the question

  • Saying the context is a mutable map you can put() into
  • Confusing CoroutineContext with CoroutineScope (scope wraps a context but is not the same)
  • Thinking only the dispatcher lives in the context
  • Believing you must always create a context manually rather than inheriting it
  • Claiming + mutates the left-hand context

context

open as a page

What is a CoroutineDispatcher, and when would you use Dispatchers.Default versus Dispatchers.IO?

level: juniorimportance: must knowfreq 85%

basics

~10 s

A dispatcher decides which thread a coroutine runs on. Use Default for heavy CPU work like sorting or parsing, and IO for waiting on network or disk calls.

open as a page

What does Dispatcher.limitedParallelism(n) do, and why would you use it?

level: juniorimportance: should knowfreq 45%

basics

~20 s

It creates a view of a dispatcher that lets at most n coroutines run at the same time. You use it to cap how much work hits a limited resource, like a database or an external API.

open as a page

Explain the Element/Key model of CoroutineContext: how does context[CoroutineName] resolve an element, and why is each element also a CoroutineContext?

level: middleimportance: should knowfreq 45%

basics

~20 s

Each setting in the context is an Element tagged with a unique Key. You look one up by indexing with its key. Every single element is itself a tiny one-item context, which is why you can add them together with +.

open as a page

How does the + operator combine coroutine contexts, and what are its precedence/replacement rules when the same kind of element appears twice?

level: middleimportance: should knowfreq 40%

basics

~20 s

The + operator merges two contexts into a new one. If both sides contain the same kind of element (say two dispatchers), the one on the right wins and replaces the one on the left.

open as a page

What is Dispatchers.Main, what does it require, and how does Dispatchers.Main.immediate differ from it?

level: middleimportance: should knowfreq 60%

basics

~10 s

Dispatchers.Main runs coroutines on the app's UI thread so you can safely update the screen. Its immediate variant skips re-scheduling and runs right away if you're already on that thread.

open as a page

What does Dispatchers.Unconfined do, and why is it generally discouraged in production code?

level: middleimportance: should knowfreq 45%

basics

~10 s

Unconfined doesn't tie a coroutine to any particular thread. It starts on the calling thread, and after a pause it continues on whatever thread woke it up. That unpredictability makes it risky.

open as a page

How can you protect shared mutable state in coroutines using single-threaded confinement, and how does limitedParallelism(1) compare to newSingleThreadContext()?

level: middleimportance: should knowfreq 55%

basics

~20 s

Run all updates to the shared state on a dispatcher that uses only one thread, so they happen one at a time and never race. limitedParallelism(1) does this by borrowing one slot of an existing pool; newSingleThreadContext creates a dedicated thread you must close.

open as a page

You need to limit concurrent calls to a downstream service to 10. Why prefer Dispatchers.IO.limitedParallelism(10) over creating Executors.newFixedThreadPool(10).asCoroutineDispatcher()?

level: middleimportance: should knowfreq 40%

basics

~20 s

The limitedParallelism view reuses the shared IO threads and needs no shutdown, so it's cheaper and safer. A custom fixed pool creates 10 dedicated threads you must shut down yourself, and they sit idle when unused.

open as a page

How do you build a custom CoroutineDispatcher from a java.util.concurrent.Executor, and what must you manage about its lifecycle?

level: seniorimportance: should knowfreq 40%

basics

~10 s

Wrap an existing Java thread pool with asCoroutineDispatcher() to get a dispatcher coroutines can use. Because you created the pool, you are responsible for shutting it down when done.

open as a page

Explain the precise semantics of limitedParallelism: how parallelism is counted, what happens to excess coroutines, and the pitfalls of chaining or misusing it (e.g. on Dispatchers.Main or Unconfined).

level: seniorimportance: should knowfreq 30%

basics

~20 s

It limits how many coroutines run at once on that view; extras queue and run as slots free up. Each call returns an independent view with its own limit. It's meant for multi-threaded dispatchers like IO/Default, not for Main or Unconfined.

open as a page

Compare passing a dispatcher to launch/async versus using withContext to switch dispatchers. When is each correct, and what are the structured-concurrency and performance implications?

level: principalimportance: should knowfreq 35%

basics

~20 s

Give launch or async a dispatcher to start a new background job on it. Use withContext to temporarily move an existing coroutine's work to another dispatcher and come back. withContext is best for switching inside a function.

open as a page

Design a custom CoroutineContext element to carry request-scoped data (e.g. a trace id) through coroutines. How does this compare to a ThreadLocal, and what are the pitfalls?

level: seniorimportance: nice to knowfreq 25%

basics

~20 s

Make a small class that extends the coroutine-context element base and give it a companion Key. Add it to a coroutine's context and read it anywhere with context[Key]. Unlike a ThreadLocal, it follows the coroutine across threads automatically.

open as a page

What do minusKey and fold do on a CoroutineContext, and when would you use them?

level: seniorimportance: nice to knowfreq 22%

basics

~10 s

minusKey returns a new context with one kind of element removed. fold lets you walk over every element in the context, for example to log or copy them. Both leave the original context unchanged.

open as a page

Compare single-threaded confinement (limitedParallelism(1)) against Mutex and the actor pattern for protecting shared mutable state under high contention. When does each win, and what are the failure modes?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

Confinement runs all updates on one thread so they can't collide; a Mutex lets work run on many threads but takes turns at the critical section; an actor uses one coroutine reading a channel of messages. Confinement and actors serialize ownership; a Mutex guards a section.

open as a page