Kotlin Coroutines Basics

A coroutine is a piece of code that can pause its execution without blocking the thread it runs on, and resume later from where it left off. Coroutines make it easy to write code that fetches data from the internet, reads files, or waits for something — all without freezing your app.

The Problem — Why Blocking is Bad

Imagine you have one worker (one thread) at a restaurant.

Blocking approach:
Worker takes order → WAITS at the kitchen (thread blocked) →
Kitchen finishes → Serves food → Takes next order
The counter is empty while the worker waits.

Coroutines approach:
Worker takes order → Goes to next customer while food is prepared
→ Kitchen signals food is ready → Worker returns and serves food
The counter never stops moving.

On Android, the main thread runs the UI. If you block it with a network call, the app freezes. Coroutines keep it free.

Adding Coroutines to Your Project

// In build.gradle.kts (app level)
dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}

Your First Coroutine

import kotlinx.coroutines.*

fun main() = runBlocking {
    println("Start")
    launch {
        delay(1000L)               // pause 1 second (non-blocking)
        println("Coroutine done")
    }
    println("After launch")
}

// Output:
// Start
// After launch
// Coroutine done   ← arrives 1 second later

Diagram — Coroutine Execution Flow

Time →  0ms       1ms       1000ms
        │         │         │
main:   Start ──► After ──► (waiting)
                  launch
        │
launch: (started)──────────────► Coroutine done
        paused with delay(1000)

Key Coroutine Concepts

suspend Functions

A suspend function can pause without blocking. It can only be called from another suspend function or a coroutine.

suspend fun fetchData(): String {
    delay(2000L)   // simulate network delay
    return "User data loaded"
}

fun main() = runBlocking {
    println("Fetching...")
    val data = fetchData()    // pauses here, does not block thread
    println(data)
}
// Fetching...
// User data loaded   (2 seconds later)

Coroutine Builders

Builder        | Returns  | Behavior
---------------|----------|------------------------------------------
runBlocking    | T        | Blocks the thread — for main() and tests only
launch         | Job      | Fire-and-forget; does not return a value
async          | Deferred | Returns a value; use await() to get it

launch — Fire and Forget

fun main() = runBlocking {
    val job = launch {
        delay(500)
        println("Background task complete")
    }
    println("Main continues")
    job.join()   // wait for the job to finish
    println("Done")
}
// Main continues
// Background task complete
// Done

async / await — Concurrent with a Result

fun main() = runBlocking {
    val result1 = async { fetchScore("Alice") }
    val result2 = async { fetchScore("Bob") }

    val total = result1.await() + result2.await()
    println("Total: $total")
}

suspend fun fetchScore(name: String): Int {
    delay(1000L)
    return if (name == "Alice") 90 else 85
}
// Both fetches run concurrently — total wait ~1 second, not 2

Coroutine Scope

Every coroutine runs in a scope. The scope controls the coroutine's lifetime. When the scope is cancelled, all coroutines inside it are cancelled too.

Scope               | Where to use
--------------------|--------------------------------------------
runBlocking         | Tests and main() only
GlobalScope         | App-wide (avoid — no lifecycle awareness)
viewModelScope      | Android ViewModel (cancelled with ViewModel)
lifecycleScope      | Android Activity/Fragment
CoroutineScope      | Custom scope you manage

Coroutine Dispatchers

A dispatcher decides which thread (or thread pool) a coroutine runs on.

Dispatcher             | Runs on
-----------------------|-------------------------------------------
Dispatchers.Main       | Main (UI) thread — for UI updates
Dispatchers.IO         | Thread pool for file/network operations
Dispatchers.Default    | CPU-intensive work (sorting, parsing)
Dispatchers.Unconfined | Current thread (rarely used)
import kotlinx.coroutines.*

fun main() = runBlocking {
    launch(Dispatchers.IO) {
        println("Running on IO: ${Thread.currentThread().name}")
        val data = simulateFetch()
        withContext(Dispatchers.Main) {
            println("Update UI: $data")
        }
    }
}

suspend fun simulateFetch(): String {
    delay(500)
    return "Server response"
}

Diagram — Switching Dispatchers

[Dispatchers.IO]                 [Dispatchers.Main]
Network call (background)   →    Update UI (main thread)
simulateFetch()             →    withContext(Main) { ... }

Thread blocked doing work?  No.  coroutine suspends, thread is free.

Leave a Comment

Your email address will not be published. Required fields are marked *