skip to content

Explain the Worker API on Kotlin/Native: how do you start one, run work on it, and get results back?

level: middleimportance: should knowfreq 38%

answer

  1. Worker.start() spins an OS thread
  2. execute(mode, producer){ operation }
  3. future.result blocks for the value
  4. requestTermination() to stop
  5. Prefer coroutine dispatchers for app code

basics

~10 s

A Worker is a background thread. You start it with Worker.start(), send work with execute(), which returns a Future. You read the answer from future.result, and stop the worker with requestTermination().

solid answer

~40 s

Worker (in kotlin.native.concurrent) is Kotlin/Native's low-level OS-thread abstraction. Worker.start() creates a backing thread with its own run loop. You schedule work with worker.execute(mode, producer) { ... }: the producer lambda computes the input passed to the operation lambda, which runs on the worker thread and returns a result. execute returns a Future<T>; calling future.result blocks the caller until the job finishes and yields the value (or rethrows). You shut down with worker.requestTermination(processScheduledJobs = true).result, which returns a Future you can await. Under the new memory manager the producer/operation can capture and return shared objects without freezing — the TransferMode argument is now effectively legacy. Workers are a primitive layer; most application code uses coroutine dispatchers built on threads (Dispatchers.Default) instead of managing Workers directly.

code

kotlin · 12 lines
kotlin
import kotlin.native.concurrent.Worker
import kotlin.native.concurrent.TransferMode

fun heavy(n: Int): Long = (1..n).fold(0L) { a, x -> a + x }

fun main() {
    val w = Worker.start()
    val f = w.execute(TransferMode.SAFE, { 1_000_000 }) { heavy(it) }
    // ... do other things on main thread ...
    println(f.result)                      // blocks until computed
    w.requestTermination().result
}

go deeper

for a junior

Knows Worker.start, execute, future.result, requestTermination at a high level.

for a middle

Explains producer-vs-operation thread split and blocking semantics of future.result.

for a senior

Notes TransferMode is legacy under the NMM, mentions executeAfter and when to prefer dispatchers.

for a principal

Weighs Worker vs coroutine schedulers for a library, reasons about thread-leak lifecycle and structured shutdown across an app.

## What a Worker is `Worker` lives in `kotlin.native.concurrent`. It wraps an **OS thread** with a message/run loop. It is the lowest-level threading primitive Kotlin/Native exposes — coroutine dispatchers are built on top of threads like these. ## Lifecycle ```kotlin import kotlin.native.concurrent.Worker import kotlin.native.concurrent.TransferMode fun main() { val worker = Worker.start(name = "job-runner") val future = worker.execute( TransferMode.SAFE, // legacy under the new MM { 21 } // producer: runs on caller, produces input ) { input -> // operation: runs on the worker thread input * 2 } println(future.result) // blocks caller, prints 42 worker.requestTermination(processScheduledJobs = true).result } ``` ### Steps - **`Worker.start()`** — spins up the backing thread; optional `name`, `errorReporting`. - **`execute(mode, producer, operation)`** — schedules a job. The `producer` lambda runs **synchronously on the calling thread** and returns the argument; the `operation` lambda runs **on the worker thread** with that argument and returns the result. Two lambdas exist so the boundary input could historically be detached. - **`Future<T>`** — handle to the eventual result. `future.result` **blocks** until done and returns the value, rethrowing any exception thrown by the operation. `future.state` lets you inspect status; `future.consume { }` processes it. - **`requestTermination(processScheduledJobs)`** — asks the worker to stop, optionally draining queued jobs; returns a `Future<Unit>` you await via `.result`. ## New memory manager effects Before the NMM, the `producer` result had to be transferable (frozen or detached) per `TransferMode`. Now objects are simply **shared**, so producer/operation can capture outer state and return mutable graphs. `TransferMode.SAFE`/`UNSAFE` remains in the signature for compatibility but no longer enforces detachment. ## executeAfter and the run loop `worker.executeAfter(afterMicroseconds, operation)` schedules delayed work on the worker's loop — useful for timers. The worker keeps processing until terminated. ## When NOT to use Worker directly For most app/library code, prefer **coroutines** with `Dispatchers.Default`/`Dispatchers.IO` (multi-threaded under the NMM) or `newSingleThreadContext`. Raw `Worker` is for low-level control, interop glue, or building your own scheduler.

  • Why does Worker.execute take two lambdas (producer and operation) instead of one?
    The producer runs on the caller to compute the input, and the operation runs on the worker. The split existed so the input could be detached/transferred across the boundary in the old model; under the NMM it is mostly historical.
  • Does future.result block, and what happens if the operation threw?
    Yes, future.result blocks the calling thread until the job completes. If the operation threw, future.result rethrows that exception on the caller.

A Worker is a hired assistant at a desk: you hand them a task slip (execute), they work while you do other things, and you pick up the finished result later (future.result).

saying these in an interview costs you the question

  • Saying Worker.execute returns the value directly instead of a Future
  • Forgetting to terminate the worker, leaking a thread
  • Claiming the operation lambda runs on the calling thread
  • Thinking you must freeze the producer result under the new MM
  • Recommending raw Workers where coroutine dispatchers fit better

context