skip to content

What problem do Kotlin's context parameters (context(...)) solve, how do they differ from an extension receiver, and how do you declare and call a function that requires a context?

level: middleimportance: should knowfreq 35%

answer

  1. context(name: Type) adds implicit deps beyond the 1 extension + 1 dispatch receiver
  2. named context value, not the this/dot-receiver
  3. caller must have the context in scope (e.g. with(x){ })
  4. contexts propagate through the call graph
  5. experimental: -Xcontext-parameters opt-in (Kotlin 2.2)

basics

~20 s

Context parameters let a function require some surrounding object (like a logger or transaction) to be implicitly available, without it being a normal parameter or the receiver. You declare context(x: T) before the function and the caller must have a matching context in scope.

solid answer

~50 s

A function can have at most one dispatch receiver and one extension receiver. When you need extra implicit dependencies (logger, transaction, clock) without threading them as explicit parameters, you use context parameters: context(logger: Logger) fun doWork(). At the call site the named context value must be available — typically provided by a context block such as with(logger) { doWork() } or via another function that already declares the same context. Unlike an extension receiver, a context parameter is not the this of the body addressed by member-style calls; in the modern design (Kotlin 2.2, declared with context(name: Type)) it is a named value you reference explicitly, and it does NOT become the dot-receiver of doWork — you don't call value.doWork(). Context parameters propagate: a function declaring a context can call other functions needing the same context implicitly. The feature is still experimental and requires opt-in (-Xcontext-parameters).

code

kotlin · 17 lines
kotlin
// requires -Xcontext-parameters
interface Logger { fun info(m: String) }

context(logger: Logger)
fun greet(name: String) {
    logger.info("Hello, $name")   // reference the context by name
}

context(logger: Logger)
fun run() {
    greet("Ada")   // Logger context propagates implicitly
}

fun main() {
    val log = object : Logger { override fun info(m: String) = println(m) }
    with(log) { run() }   // provide the Logger context
}

go deeper

for a junior

Knows context parameters add ambient dependencies but may not know the syntax or opt-in.

for a middle

Declares context(name: Type), explains caller obligation and propagation, distinguishes from extension receiver.

for a senior

Discusses when context params beat plain parameters, the experimental status, and migration from context receivers.

for a principal

Sets policy on using an experimental feature in shared code and designs capability/effect-style APIs around contexts.

## Why context parameters exist Kotlin lets a function have **one dispatch receiver** (the class it's a member of) and **one extension receiver** (the `T` in `fun T.foo()`). That's only two implicit objects. Real code often needs more ambient dependencies — a `Logger`, a `Transaction`, a `Clock`, a `CoroutineScope` — and threading them as explicit parameters everywhere is noisy. **Context parameters** add extra *implicit* parameters that the **caller** must satisfy, but that you don't repeat at every call. ## Declaring them The modern (Kotlin 2.2) syntax uses a `context(...)` clause with **named** parameters: ```kotlin context(logger: Logger) fun doWork() { logger.info("working") // reference the context by its name } ``` This replaces the earlier *context receivers* prototype (`context(Logger)` with no name, where members were accessed as if `this`). The named form is clearer and is the direction Kotlin took. ## How it differs from an extension receiver - Extension receiver: `fun Logger.doWork()` — `this` is the `Logger`, and you call it as `someLogger.doWork()`. - Context parameter: `context(logger: Logger) fun doWork()` — `doWork()` is **not** called on a logger (`logger.doWork()` is wrong); it's called as a top-level `doWork()` and the compiler verifies a `Logger` is *in context*. You reference it by name (`logger`), not as `this`. ## Providing a context at the call site A context is satisfied when a value of that type is in scope as a context. Common ways: - A surrounding function that itself declares the same context parameter. - `with(logger) { doWork() }` — `with` makes `logger` available as a context for calls inside. ```kotlin fun main() { val log = ConsoleLogger() with(log) { doWork() } // Logger context satisfied } ``` ## Propagation Context parameters **flow** through the call graph: any function that declares `context(logger: Logger)` can call `doWork()` (which also needs `Logger`) without re-providing it. This is what makes them ergonomic for cross-cutting concerns. ## Status & opt-in Context parameters are **experimental** in Kotlin 2.2 and require the compiler flag `-Xcontext-parameters`. They replaced the older `-Xcontext-receivers` prototype, which is being removed. Don't assume they're available without opt-in, and avoid them in stable public APIs. ## When to reach for them - Cross-cutting ambient services (logging, transactions, dependency scopes). - DSLs that need several capabilities in scope at once, beyond what one extension receiver allows. Not a replacement for ordinary parameters when the dependency is genuinely data, not ambient context.

  • Can you call greet("Ada") without any Logger in scope?
    No. The compiler rejects it: greet requires a Logger context, so a value of that type must be available as a context (e.g. via with(log) { } or a caller that declares the same context).
  • Is logger.greet("Ada") a valid way to call a context-parameter function?
    No. A context parameter is not the dot-receiver. greet is called as greet("Ada"); the context is supplied implicitly, not via dot syntax.

saying these in an interview costs you the question

  • Thinks context parameters are just normal parameters with sugar and add no caller obligation
  • Calls a context-parameter function as context.fn() like an extension
  • Claims the feature is stable / available without an opt-in flag
  • Confuses the removed context-receivers prototype with named context parameters
  • Believes a function can have many extension receivers instead of using contexts

context