What does withContext do, and how would you use it to run blocking I/O off the main thread?
answer
- Suspends, runs block in new context, returns result
- withContext(Dispatchers.IO) = offload blocking I/O
- No new coroutine, runs sequentially
- Resumes on caller's dispatcher automatically
- Makes suspend functions main-safe
basics
~20 swithContext runs a block of suspending code on a different thread pool and waits for its result. You wrap blocking I/O in withContext(Dispatchers.IO) so it does not freeze the main thread, then it returns the value.
solid answer
~40 swithContext(context) is a suspending function that switches the coroutine's CoroutineContext (typically the dispatcher) for the duration of a block, runs the block, and returns the block's last expression as a value. The classic idiom is withContext(Dispatchers.IO) { ... } to move blocking calls (JDBC, file reads, blocking HTTP) off the main/UI thread or a constrained dispatcher onto the I/O thread pool. Unlike launch/async it does not create a new concurrent coroutine — it runs sequentially in the same coroutine and suspends the caller until done. When the block finishes, the coroutine resumes back on the original dispatcher automatically. It is the standard way a suspend function makes itself main-safe: callers can invoke it from any dispatcher and it offloads internally.
code
kotlin · 3 linessuspend fun fetchConfig(): String = withContext(Dispatchers.IO) {
java.io.File("config.txt").readText() // blocking, but on the IO pool
}go deeper
Knows withContext(Dispatchers.IO) offloads blocking I/O and returns a result.
Explains main-safety and that it resumes on the original dispatcher automatically.
Contrasts with launch/async, notes it is a cancellable suspension point and the cheapest single-offload primitive.
Frames main-safety as a library design contract and reasons about dispatcher selection for throughput.
## What `withContext` is `withContext(context: CoroutineContext, block: suspend () -> T): T` is a **suspending function** from `kotlinx.coroutines`. It runs `block` with the given context elements merged onto the current coroutine's context, **suspends the calling coroutine until the block completes**, and **returns the block's result**. The most common element you pass is a **`CoroutineDispatcher`** — the object that decides which thread the coroutine runs on: - `Dispatchers.Default` — CPU-bound work, pool sized to CPU cores. - `Dispatchers.IO` — blocking I/O (JDBC, file, blocking sockets); large elastic pool. - `Dispatchers.Main` — the UI thread (Android/JavaFX/Swing). - `Dispatchers.Unconfined` — rarely used; starts in the caller thread. ## Why it matters: main-safety A `suspend` function should be **main-safe** — callable from any dispatcher without blocking it. You achieve that by wrapping blocking code internally: ```kotlin suspend fun loadUser(id: Long): User = withContext(Dispatchers.IO) { jdbcTemplate.queryForObject(/* blocking */) // runs on IO pool } ``` The caller can run on `Dispatchers.Main`; `withContext` hops to `IO`, runs the blocking call, then **resumes back on the caller's original dispatcher** when the block ends. You do not switch back manually. ## How it differs from `launch`/`async` - `launch` returns a `Job`, `async` returns a `Deferred<T>` — both start a **new concurrent coroutine**. - `withContext` starts **no new coroutine**; it runs the block **sequentially** in the same coroutine and returns the value directly. There is nothing to `await`. So `val x = async(ctx) { f() }.await()` is functionally close to `val x = withContext(ctx) { f() }`, but `withContext` is the idiomatic, cheaper choice for a single sequential offload. ## Return value and cancellation - The block's **last expression** is the return value. - `withContext` is a **cancellable suspension point**: if the parent coroutine is cancelled it throws `CancellationException`. ## Recap - Use it to **change dispatcher** for a block and get a result. - The idiom for offloading blocking work is `withContext(Dispatchers.IO) { ... }`.
- After withContext(Dispatchers.IO) { ... } returns, which dispatcher does the coroutine continue on?It resumes on whatever dispatcher the caller was on before the call; the IO switch only applies inside the block.
- Does withContext start a new coroutine?No. It runs the block in the same coroutine, just with a modified context, and suspends until it completes.
Like stepping into a different room to do a noisy task, then walking back to your desk with the result.
saying these in an interview costs you the question
- Thinking withContext starts a concurrent coroutine like launch
- Saying you must manually switch the dispatcher back afterward
- Using Dispatchers.IO for CPU-bound work instead of Default
- Believing withContext blocks the calling thread (it suspends, not blocks)