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 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.
import kotlinx.coroutines.*
fun main() = runBlocking {
// Builders run inside CoroutineScope
val job = launch {
delay(1000L)
println("World!")
}
println("Hello,")
job.join()
}
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.
| Builder | Return type | Thread blocking | Scenario |
|---|---|---|---|
| launch | Job | No | Fire-and-forget tasks |
| async | Deferred<T> | No | Parallel computations |
| runBlocking | T | Yes | Tests, main function |
| produce | ReceiveChannel<E> | No | Streaming (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 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.
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.
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 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.
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.
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 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.
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.
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.
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.
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.
Frequently Asked Questions
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.
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.
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).
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.
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
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.
Read also