iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Coroutines

Coroutines are Kotlin’s answer to async. Cooperative, lightweight (millions on one machine), composable with structured concurrency. suspend functions can pause without blocking the thread.

launch, async, withContext, structured cancellation

EXAMPLE
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

// 1) suspend function — non-blocking
suspend fun fetchUser(id: Int): User {
    return withContext(Dispatchers.IO) {
        api.getUser(id)        // blocking I/O happens off the calling thread
    }
}

// 2) launch — fire and forget (returns a Job)
fun loadProfile(scope: CoroutineScope, uid: Int) {
    scope.launch {
        val user = fetchUser(uid)
        ui.show(user)
    }
}

// 3) async — parallel results
suspend fun loadDashboard(): Dashboard = coroutineScope {
    val user    = async { fetchUser(uid) }
    val orders  = async { fetchOrders(uid) }
    val posts   = async { fetchPosts(uid) }
    Dashboard(user.await(), orders.await(), posts.await())
}

// 4) Structured concurrency — child jobs share parent's lifetime
suspend fun safeFanOut() = coroutineScope {
    launch { workOne() }
    launch { workTwo() }
    // If either fails or the parent is cancelled, BOTH are cancelled.
}

// 5) Switch context
suspend fun saveImage(bytes: ByteArray) {
    val processed = withContext(Dispatchers.Default) { resize(bytes) }   // CPU
    withContext(Dispatchers.IO) { file.writeBytes(processed) }            // I/O
}

// 6) Timeout
suspend fun robust() {
    try {
        withTimeout(5.seconds) {
            slowApi.call()
        }
    } catch (e: TimeoutCancellationException) {
        // handle
    }
}

// 7) Cancellation — cooperative, must check
fun heavyLoop() = launch {
    repeat(1_000_000) { i ->
        ensureActive()                     // cancellation check
        if (i % 1000 == 0) compute(i)
    }
}

// 8) Exception handling
val handler = CoroutineExceptionHandler { _, e -> log.error("unhandled", e) }
scope.launch(handler) { riskyWork() }

// 9) Android — viewModelScope / lifecycleScope
// In a ViewModel:
fun login(email: String, password: String) = viewModelScope.launch {
    _state.value = State.Loading
    _state.value = runCatching { auth.login(email, password) }
        .fold({ State.Success(it) }, { State.Failure(it.message ?: "err") })
}

// 10) Flow — async streams
val messages: Flow<Message> = flow {
    while (true) {
        emit(api.pollNext())
        delay(1000)
    }
}

viewModelScope.launch {
    messages
        .filter { it.unread }
        .map { it.copy(rendered = render(it)) }
        .flowOn(Dispatchers.Default)
        .collect { ui.append(it) }
}

// 11) StateFlow / SharedFlow — hot state holders
private val _ui = MutableStateFlow<UiState>(UiState.Idle)
val ui: StateFlow<UiState> = _ui.asStateFlow()

// 12) Best practices
//   • Don't run GlobalScope.launch — use a structured scope tied to lifecycle
//   • Use Dispatchers.IO for blocking I/O, .Default for CPU, .Main for UI
//   • Mark functions suspend when they can pause; never block in them

Why it matters

Structured concurrency makes leaks impossible: every coroutine is owned by a scope and dies with it. Avoid GlobalScope — pair every launch with a lifecycle-aware scope (Android: viewModelScope; server: a job tied to the request).

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        delay(500)
        println("hi from coroutine")
    }
    job.join()
}
Try it Yourself »

Exercise

Fire-and-forget coroutine builder.

scope. { logEvent() }

Test yourself

Q1. A lightweight unit of concurrency in Kotlin is…
Q2. Inside a coroutine, suspend a call with…
Q3. Fire-and-forget coroutine is launched with…

Discussion

Loading…