Coroutine Builder: what it is, types of coroutine builders and how they work

Author: IT Sectr Published: 2026-06-21 Reading time: 8 min

Coroutine Builder — Kotlin Coroutines functions that create and launch coroutines, defining how they execute. The builders launch, async, runBlocking and produce cover different scenarios: from background tasks to parallel computations with result return. According to JetBrains, 2024, Coroutine Builder is the foundation of the coroutine model, providing structured concurrency and lifecycle management.

Key Takeaways

  • Coroutine Builder — a function that creates a coroutine within a specific CoroutineScope
  • launch — launches a coroutine without returning a result, returns a Job object
  • async — launches a coroutine returning Deferred, allowing result retrieval via await()
  • runBlocking — blocks the current thread, used as a bridge between blocking and suspending code
  • produce — creates a coroutine with a channel for streaming data (deprecated in favor of Flow)

What is a Coroutine Builder?

Coroutine Builder is a Kotlin extension function that takes a CoroutineScope and a suspend block, creating and launching a new coroutine. Each builder defines how the coroutine will execute: with or without a result return, with thread blocking or asynchronously. Builders are the entry points into the language's coroutine model.

All builders work through CoroutineScope, which manages the lifecycle of child coroutines. When a scope is cancelled, all coroutines launched through it are automatically cancelled — this is the principle of structured concurrency. This approach prevents coroutine leaks and guarantees predictable termination.

kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    // Builders run inside CoroutineScope
    val job = launch {
        delay(1000L)
        println("World!")
    }
    println("Hello,")
    job.join()
}

Main types of coroutine builders

Kotlin provides four built-in coroutine builders: launch, async, runBlocking and produce. Each has its own return type and area of application. For Android mobile development, the main ones are launch and async — they work in a non-blocking manner and integrate with architectural components.

BuilderReturn typeThread blockingScenario
launchJobNoFire-and-forget tasks
asyncDeferred<T>NoParallel computations
runBlockingTYesTests, main function
produceReceiveChannel<E>NoStreaming (deprecated)

Each builder accepts additional parameters: CoroutineStart (launch strategy), CoroutineContext (dispatcher, exceptions) and a named code block. By default, the coroutine starts immediately (CoroutineStart.DEFAULT).

launch: fire-and-forget execution

launch is the most used builder in Android development. It launches a coroutine that does not return a result, and returns a Job object for managing its lifecycle. This is the ideal choice for operations that only need a side effect: saving to a database, sending analytics, updating the UI.

Syntax and usage of launch

The launch builder accepts CoroutineScope, an optional CoroutineContext, and a suspend block. The returned Job allows you to cancel the coroutine, wait for its completion, or check its status.

kotlin
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
val job: Job = scope.launch(CoroutineStart.LAZY) {
    val data = fetchFromNetwork()
    saveToDatabase(data)
}
job.start()
job.join()

The CoroutineStart.LAZY parameter defers execution until an explicit call to start() or join(). This is useful for lazy initialization and conditional execution. For standard immediate execution, CoroutineStart.DEFAULT is used or the parameter is omitted.

async: parallel computations with result

async is a builder that returns Deferred<T> — an asynchronous promise of a result. Calling await() suspends the coroutine until the result is obtained, without blocking the thread. This is the primary mechanism for parallel tasks in Kotlin coroutines.

Launching two requests in parallel

async is especially effective when you need to execute multiple independent operations simultaneously. Unlike sequential calls to suspend functions, async launches coroutines in parallel, reducing total execution time.

kotlin
suspend fun fetchUserData(): UserData {
    val deferred1 = CoroutineScope(Dispatchers.IO).async { api.getProfile() }
    val deferred2 = CoroutineScope(Dispatchers.IO).async { api.getSettings() }
    val deferred3 = CoroutineScope(Dispatchers.IO).async { api.getNotifications() }
    return UserData(
        profile = deferred1.await(),
        settings = deferred2.await(),
        notifications = deferred3.await()
    )
}

Deferred inherits from Job, so async supports all lifecycle operations: cancellation, completion waiting, exception handling. When a scope is cancelled, child Deferred coroutines are cancelled automatically.

runBlocking: bridge to the blocking world

runBlocking is the only builder that blocks the current thread until the coroutine completes. It creates a new CoroutineScope and launches the passed coroutine, blocking the calling thread. It is used in main() entry points, in tests, and when integrating with blocking code.

When runBlocking is necessary

runBlocking is justified in three scenarios: the application entry point (main), unit tests of suspend functions, and integration with callback-based libraries where suspend cannot be used. In production Android code, using runBlocking on the main thread is strongly not recommended.

kotlin
class CoroutineTest {
    @Test
    fun `test suspend function`() = runBlocking {
        val result = mySuspendFunction()
        assertEquals("expected", result)
    }
}

For tests, it is recommended to use kotlinx-coroutines-test with TestCoroutineDispatcher instead of runBlocking — this provides time control and avoids blocking in the test environment.

How to choose the right builder

The choice of Coroutine Builder depends on the return result and the execution scenario. If the operation does not require returning data — use launch. If you need the result of an asynchronous operation — use async. runBlocking should only be used for bridging, and replace produce with Flow for reactive streams.

Practical recommendations for Android

In Android projects using Kotlin Coroutines, the main pair of builders are launch and async. launch is used in ViewModel and UseCases to launch coroutines, while async is used for parallel requests to the network or database. Modern libraries (Ktor, Room) already support suspend functions, which minimizes the need for direct async usage.

  • launch — for fire-and-forget (logging, analytics, caching)
  • async — for parallel requests with result merging
  • runBlocking — only in main() and tests (not on the Main thread in production)
  • produce — replace with Flow / SharedFlow / StateFlow

Frequently Asked Questions

What is the difference between launch and async in Kotlin?

launch returns Job and does not return an execution result, while async returns Deferred<T> — an object from which the result can be obtained via await(). launch is used for fire-and-forget operations, async for tasks that return data.

Can runBlocking be used in an Android Activity?

Not recommended. runBlocking on the main thread causes ANR and blocks the UI. Use lifecycleScope.launch inside Activity and Fragment — it is a built-in solution without blocking.

What does the launch builder return?

The launch builder returns a Job object, which allows you to control the coroutine lifecycle: cancel (cancel), wait for completion (join), check status (isActive, isCompleted, isCancelled).

What is Deferred in Kotlin Coroutines?

Deferred<T> is an asynchronous promise of a result, returned by the async builder. It inherits from Job and adds methods await() to get the result, getCompleted() for non-blocking access, and getCompletionExceptionOrNull() for checking exceptions.

How to launch with delay?

Use the CoroutineStart.LAZY parameter: scope.launch(start = CoroutineStart.LAZY) { ... }. Then call job.start() or job.join() for actual execution. This is useful for lazy initialization and conditional coroutine execution.

Summary

  • Coroutine Builder — a coroutine creation function that defines how it executes and its return type
  • launch — the main builder for tasks without a result, returns Job for coroutine management
  • async — builder for parallel computations, returns Deferred with the ability to await() the result
  • runBlocking — blocking builder for main() and tests, not recommended on the Android main thread
  • Structured concurrency guarantees automatic cancellation of child coroutines when the scope is cancelled
  • produce is deprecated — use Flow, SharedFlow or StateFlow for reactive data streams
  • The choice of builder depends on the scenario: fire-and-forget → launch, parallel data → async, bridging → runBlocking

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also