skip to content

runBlocking

runBlocking blocks the calling thread until the coroutine inside finishes, which is exactly what you want in main and in tests, and never inside another coroutine. Interviewers use it to check you know where the blocking-to-suspending bridge belongs.

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

questions

5

What is runBlocking in Kotlin coroutines, and when should you use it?

level: juniorimportance: must knowfreq 80%

answer

  1. Bridge: blocking world -> suspend world
  2. Blocks the current thread until done
  3. main() and tests only
  4. Returns the lambda's value
  5. Never inside another coroutine

basics

~10 s

runBlocking is a bridge that lets regular blocking code call suspend functions. It blocks the current thread until the coroutines inside finish. Use it in main() and tests, not inside other coroutines.

solid answer

~40 s

runBlocking is a coroutine builder that bridges the blocking world to the suspending world. It starts a new coroutine on the current thread and blocks that thread until all work inside its block (and child coroutines) completes, then returns the block's result. Unlike launch/async, it is a regular (non-suspend) function, so you can call it from ordinary code. Its typical uses are entry points that aren't suspend: the main() function of a small program/CLI and unit tests (though tests usually prefer runTest). Inside its lambda you have a CoroutineScope and can call suspend functions and other builders. You should NOT call runBlocking from inside an existing coroutine or suspend function, because it blocks the underlying thread and can stall a dispatcher's thread pool, defeating the point of coroutines.

code

kotlin · 11 lines
kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {   // expression-body main, common idiom
    val greeting = fetchGreeting()
    println(greeting)
}

suspend fun fetchGreeting(): String {
    delay(200)               // simulate suspending work
    return "hello"
}

go deeper

for a junior

Knows runBlocking bridges blocking to suspend code and blocks the thread until done; uses it in main/tests.

for a middle

Explains it returns the block's value, propagates exceptions, and that runTest is the preferred test tool.

for a senior

Articulates thread-pool starvation risks when nesting it, and contrasts it with withContext/coroutineScope.

for a principal

Frames runBlocking as a controlled interop boundary policy across a codebase and bans it from hot/coroutine paths via lint/conventions.

## The problem runBlocking solves Kotlin coroutines split code into two worlds. **Suspend functions** (declared with the `suspend` keyword) can pause and resume without blocking a thread, but they can only be called from other suspend functions or from inside a coroutine. **Ordinary functions** (like `main()` before Kotlin made it suspend-capable, or a JUnit test method) cannot call suspend functions directly. `runBlocking` is the **bridge** between these two worlds. ## What it does ```kotlin import kotlinx.coroutines.* fun main() { val result = runBlocking { // this: CoroutineScope delay(100) // suspend function, legal here compute() // another suspend function } println(result) } suspend fun compute(): Int { delay(50); return 42 } ``` - It **creates a new coroutine** and runs the lambda you pass. - It **blocks the current thread** until that coroutine — and every child coroutine launched inside it — completes. - It **returns the value** the lambda produces (here the `Int` from `compute()`), or rethrows any exception thrown inside. - The lambda's receiver is a `CoroutineScope`, so inside you can use `delay`, `launch`, `async`, `withContext`, etc. ## When to use it - **`main()` of small apps / CLI tools** that need to run suspend code but aren't structured around a framework that already provides a scope. - **Tests**, to drive suspend code from a non-suspend test method. Modern code prefers `runTest` from `kotlinx-coroutines-test` (it skips `delay` virtually and gives better control), but `runBlocking` still works. - **Legacy / interop boundaries**: a blocking API (e.g. a servlet, a callback) that must call into suspend code. ## When NOT to use it - **Never inside another coroutine or a suspend function.** `runBlocking` blocks the carrier thread; if that thread belongs to a limited dispatcher pool (like `Dispatchers.Default`), you waste a pooled thread and risk thread-pool starvation/deadlock. The right tools there are `withContext`, `coroutineScope`, `async`/`await`. - **Not on the Android/UI main thread** in production — it freezes the UI. Use a lifecycle scope and `launch` instead. ## Key APIs and keywords - `suspend` — marks a function that can pause. - `runBlocking { ... }` — the bridge builder (returns the block's value). - `CoroutineScope` — the receiver type of the block. - `delay`, `launch`, `async`, `withContext` — used inside the block. - `runTest` — the test-specific replacement.

  • Why is runBlocking discouraged inside an existing coroutine?
    It blocks the carrier thread instead of suspending, wasting a pooled dispatcher thread and risking starvation or deadlock; use withContext/coroutineScope instead.
  • What should you usually use instead of runBlocking in tests?
    runTest from kotlinx-coroutines-test, which skips delays virtually and provides a TestScope and scheduler.

It's like a turnstile between the suspending highway and the blocking street: one person waits at the gate (the thread blocks) until everyone has passed through.

saying these in an interview costs you the question

  • Thinking runBlocking is non-blocking like launch/async
  • Using runBlocking inside a suspend function or another coroutine
  • Believing runBlocking returns immediately while work continues in background
  • Calling runBlocking on the Android UI thread in production
  • Not knowing it returns the lambda's result value

context

open as a page

How does runBlocking differ from launch and async as a coroutine builder?

level: middleimportance: must knowfreq 70%

basics

~10 s

launch and async start coroutines that run alongside your code and return right away. runBlocking is a regular function that waits, blocking the thread until everything finishes, and gives back the result.

open as a page

Explain precisely what 'blocking the current thread' means for runBlocking, including its event loop and child coroutines.

level: middleimportance: should knowfreq 50%

basics

~10 s

The thread that calls runBlocking stays busy and cannot do other work until the coroutine finishes. Inside, runBlocking runs an event loop on that thread that drives the coroutines until they all complete.

open as a page

What are the dangers of calling runBlocking from inside a coroutine or suspend function, and how do you fix such code?

level: seniorimportance: should knowfreq 45%

basics

~20 s

It blocks a real thread that the coroutine system needs. On a small thread pool this wastes or even starves threads and can deadlock. Replace it with withContext, coroutineScope, or async/await — none of which block a thread.

open as a page

How is runBlocking used in tests, and why is runTest usually preferred?

level: seniorimportance: should knowfreq 55%

basics

~10 s

runBlocking lets a normal test method call suspend functions by waiting for them. But it really sleeps through delays, making tests slow. runTest fast-forwards virtual time, so tests run instantly with more control.

open as a page