Kotlin Cheatsheet

Coroutines & Concurrency

Use this Kotlin reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Setup & core idea

Coroutines live in kotlinx-coroutines-core (not the bare stdlib):

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
}

A suspend function can pause without blocking its thread; it may only be called from another suspend function or a coroutine builder.

import kotlinx.coroutines.*

suspend fun fetchUser(id: Int): String {
    delay(100)                       // suspends, doesn't block
    return "user-$id"
}

fun main() = runBlocking {           // bridge blocking world → coroutines
    println(fetchUser(1))
}

Builders: launch, async, coroutineScope

fun main() = runBlocking {
    val job: Job = launch {                  // fire-and-forget, returns Job
        delay(50); println("side effect")
    }
    val deferred: Deferred<Int> = async { compute() }   // returns a value
    println(deferred.await())
    job.join()
}

// Parallel decomposition — both run concurrently, fail together:
suspend fun loadDashboard(): Dashboard = coroutineScope {
    val user = async { fetchUser() }
    val posts = async { fetchPosts() }
    Dashboard(user.await(), posts.await())
}

coroutineScope { } waits for all children and cancels siblings if one fails — that is structured concurrency. supervisorScope { } lets siblings survive a child's failure.

Dispatchers & withContext

val data = withContext(Dispatchers.IO) {     // switch thread pool for the block
    File("big.json").readText()
}
DispatcherUse for
Dispatchers.DefaultCPU-bound work (cores-sized pool)
Dispatchers.IOblocking IO — files, JDBC, legacy HTTP
Dispatchers.MainUI thread (Android/Compose/Swing artifact)
Dispatchers.Unconfinedrarely — tests/edge cases

withContext is the idiomatic "run this on X and give me the result"; never wrap it around launch.

Cancellation & timeouts

val job = launch {
    while (isActive) {               // cooperative: check or call suspend fns
        step()
    }
}
job.cancelAndJoin()

val result = withTimeoutOrNull(2_000) { slowCall() }   // null on timeout
withTimeout(2_000) { slowCall() }                       // throws TimeoutCancellationException

Cancellation is cooperative — delay, yield(), and most kotlinx suspend calls check it; tight CPU loops must check isActive/ensureActive(). Catch blocks must rethrow CancellationException, or the coroutine becomes uncancellable:

try { work() }
catch (e: CancellationException) { throw e }
catch (e: Exception) { log(e) }

Error handling

// async: exception surfaces at await()
val d = async { mightThrow() }
val v = runCatching { d.await() }.getOrNull()

// launch: exception propagates to the scope/parent
val handler = CoroutineExceptionHandler { _, e -> log(e) }
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default + handler)
scope.launch { risky() }             // failure won't kill sibling coroutines

Flow (cold async streams)

import kotlinx.coroutines.flow.*

fun ticks(): Flow<Int> = flow {          // cold: runs per collector
    var i = 0
    while (true) { emit(i++); delay(1_000) }
}

ticks()
    .map { it * 2 }
    .filter { it % 4 == 0 }
    .take(5)
    .flowOn(Dispatchers.Default)         // upstream context
    .collect { println(it) }             // terminal, suspends

flowOf(1, 2, 3).toList()
list.asFlow().first()

Hot state for UI/app state:

val state = MutableStateFlow(0)          // always has a value, conflated
state.value++                            // read/write without suspending
state.collect { render(it) }

val events = MutableSharedFlow<Event>()  // broadcast, no initial value
events.emit(Event.Click)

Shared mutable state

Coroutines don't remove data races. Prefer confinement or immutable messages; otherwise:

import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

val mutex = Mutex()
var counter = 0
coroutineScope {
    repeat(1_000) {
        launch(Dispatchers.Default) { mutex.withLock { counter++ } }
    }
}

// alternatives: java.util.concurrent.atomic.AtomicInteger, Channel<T> pipelines