Asynchronous Programming in Kotlin: A Complete Practical Guide

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Asynchronous Programming in Kotlin: A Complete Practical Guide



Modern applications require high performance and responsiveness. Asynchronous programming in Kotlin using coroutines has become the de facto standard for working with network requests, databases, and file operations. In this guide, we will break down the core concepts, show practical examples, and provide ready-to-use templates for your projects.



1. Coroutine Basics in Kotlin



Coroutines are lightweight threads that allow suspending the execution of a function without blocking the main thread. Unlike traditional threads, coroutines can be suspended at any point and resumed later, making them ideal for asynchronous operations.



1.1. Adding Dependencies

To start, add the dependencies in build.gradle.kts:



dependencies {    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0") // for Android}


1.2. Your First Coroutine

Let's create a simple coroutine using runBlocking — a bridge between the synchronous and asynchronous worlds:



import kotlinx.coroutines.*

fun main() = runBlocking { launch { delay(1000L) // non-blocking delay println("World!") } println("Hello,") delay(2000L) // wait for the coroutine to finish}


In this example, launch starts a new coroutine, and delay is a suspending function that does not block the thread.



2. Suspend Functions and Structured Concurrency



2.1. Creating Suspend Functions

Any function that can be suspended must be marked with the suspend modifier. Such functions can only be called from coroutines or other suspend functions:



suspend fun fetchUserData(userId: String): User {    delay(2000L) // simulating a network request    return User(userId, "Ivan Petrov")}

suspend fun fetchUserPosts(userId: String): List<Post> { delay(1500L) return listOf(Post("Title 1"), Post("Title 2"))}


2.2. Sequential Execution

By default, coroutines execute sequentially within a single launch:



fun main() = runBlocking {    val time = measureTimeMillis {        val user = fetchUserData("123")        val posts = fetchUserPosts("123")        println("${user.name} has ${posts.size} posts")    }    println("Execution time: $time ms") // ~3500 ms}


2.3. Parallel Execution with async/await

For parallel execution, use async and await:



fun main() = runBlocking {    val time = measureTimeMillis {        val user = async { fetchUserData("123") }        val posts = async { fetchUserPosts("123") }                println("${user.await().name} has ${posts.await().size} posts")    }    println("Execution time: $time ms") // ~2000 ms (parallel)}


3. Coroutine Dispatchers and Contexts



Dispatchers determine which thread or thread pool the coroutine will run on. The main dispatchers are:



  • Dispatchers.Default — for CPU-intensive tasks (default)
  • Dispatchers.IO — for I/O operations (network, files)
  • Dispatchers.Main — for the UI thread (Android, Swing)
  • Dispatchers.Unconfined — not confined to a specific thread


3.1. Example with Dispatchers.IO

fun main() = runBlocking {    launch(Dispatchers.IO) {        // Network request        val response = httpClient.get("https://api.example.com/data")        withContext(Di                

Blogs

Book Recommendations