skip to content

CoroutineContext & Elements

A CoroutineContext behaves like an immutable map keyed by element type, combined with +, which is why a dispatcher plus a CoroutineName is a valid context. Understanding that composition explains how a child inherits and overrides its parent's context.

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

questions

5

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

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

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