Coroutines

Write async code sequentially — network calls, database, and background work without blocking the UI

Basics suspend Dispatchers Async

Table of Contents

Coroutines Overview

Kotlin Coroutines simplify asynchronous programming on Android. They let you write non-blocking code that looks synchronous — no callback hell, no raw threads, and automatic cancellation when a screen is destroyed.

Main Thread (UI) │ ├── viewModelScope.launch { ... } ← starts coroutine │ │ │ ├── suspend fetchFromApi() ← pauses, doesn't block thread │ ├── withContext(IO) { ... } ← switches to background │ └── update UI state ← back on Main │ └── UI stays responsive
Old ApproachCoroutines
AsyncTask (deprecated)viewModelScope.launch
Callback chainsSequential suspend calls
Manual thread managementDispatchers.IO / Main
Manual cancellationAuto-cancel with scope lifecycle

1Coroutine Basics

A coroutine is a lightweight thread managed by Kotlin. You launch coroutines inside a CoroutineScope, and Android provides lifecycle-aware scopes so work is cancelled automatically.

Gradle dependencies

Kotlin — app/build.gradle.ktsdependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0") }

Launch a coroutine

Kotlin — Basic launch// GlobalScope — avoid in production (not lifecycle-aware) GlobalScope.launch { delay(1000L) println("Coroutine finished") } // lifecycleScope — tied to Activity/Fragment lifecycle lifecycleScope.launch { delay(1000L) binding.tvResult.text = "Loaded!" } // viewModelScope — tied to ViewModel (survives rotation) viewModelScope.launch { val data = repository.fetchData() _uiState.value = UiState.Success(data) }

CoroutineScope and Job

Kotlin — Scope and cancellationclass DataLoader { private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main) fun startLoading() { scope.launch { try { val result = loadData() showResult(result) } catch (e: CancellationException) { // coroutine was cancelled — rethrow throw e } catch (e: Exception) { showError(e.message) } } } fun cancel() { scope.cancel() // cancels all coroutines in this scope } }

launch vs async

BuilderReturnsUse When
launchJobFire-and-forget, update UI, no return value needed
asyncDeferred<T>Parallel work, need a result via await()

2suspend Functions

A suspend function can pause execution without blocking the thread. It must be called from a coroutine or another suspend function. Retrofit, Room, and most modern Android libraries expose suspend APIs.

Define and call suspend functions

Kotlin — suspend functionsuspend fun fetchUser(id: Int): User { delay(500) // simulates network delay — non-blocking return User(id, "Nikhil", "nikhil@example.com") } // Call from coroutine viewModelScope.launch { val user = fetchUser(1) _user.value = user }

Retrofit suspend API

Kotlin — ApiService.ktinterface ApiService { @GET("posts") suspend fun getPosts(): List<Post> @GET("posts/{id}") suspend fun getPost(@Path("id") id: Int): Post } class PostRepository(private val api: ApiService) { suspend fun loadPosts(): Result<List<Post>> { return try { Result.success(api.getPosts()) } catch (e: Exception) { Result.failure(e) } } }

Room suspend queries

Kotlin — TaskDao.kt@Dao interface TaskDao { @Query("SELECT * FROM tasks") suspend fun getAll(): List<Task> @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insert(task: Task) @Delete suspend fun delete(task: Task) }

Sequential suspend calls

Kotlin — Chain suspend functionssuspend fun loadUserProfile(userId: Int): UserProfile { val user = api.getUser(userId) // waits, doesn't block thread val posts = api.getPostsByUser(userId) // runs after user returns val avatar = storage.downloadAvatar(user.avatarUrl) return UserProfile(user, posts, avatar) } viewModelScope.launch { _loading.value = true try { _profile.value = loadUserProfile(42) } catch (e: Exception) { _error.value = e.message } finally { _loading.value = false } }

3Dispatchers

Dispatchers determine which thread pool runs your coroutine. Use the right dispatcher to keep the UI thread free and run heavy work on background threads.

Main dispatcher types

DispatcherThreadUse For
Dispatchers.MainMain/UI threadUpdating Views, LiveData, Compose state
Dispatchers.IOShared pool (~64 threads)Network, disk I/O, database reads/writes
Dispatchers.DefaultCPU pool (cores count)Sorting, parsing, image processing
Dispatchers.UnconfinedCaller threadRare — testing only, avoid in production

withContext — switch dispatchers

Kotlin — withContext patternclass UserViewModel : ViewModel() { fun loadUser(userId: Int) { viewModelScope.launch { // starts on Main (viewModelScope default) _loading.value = true val user = withContext(Dispatchers.IO) { // runs on background thread repository.fetchUser(userId) } // back on Main — safe to update LiveData _user.value = user _loading.value = false } } }

launch with explicit dispatcher

Kotlin — Launch on IO// Start coroutine directly on IO viewModelScope.launch(Dispatchers.IO) { val data = heavyComputation() withContext(Dispatchers.Main) { binding.tvResult.text = data } } suspend fun heavyComputation(): String { return withContext(Dispatchers.Default) { // CPU-intensive work (1..1_000_000).map { it * 2 }.last().toString() } }

Dispatcher flow diagram

viewModelScope.launch → Dispatchers.Main │ ├── withContext(IO) → Network / Room / File │ └── return result │ ├── withContext(Default) → JSON parse / sort list │ └── return result │ └── _liveData.value = ... → Update UI on Main

4Async Tasks

Use async to run multiple operations in parallel and combine results with await(). This replaces nested callbacks and is much cleaner than the deprecated AsyncTask.

async and await

Kotlin — Parallel async callsviewModelScope.launch { _loading.value = true val usersDeferred = async(Dispatchers.IO) { api.getUsers() } val postsDeferred = async(Dispatchers.IO) { api.getPosts() } // Both requests run in parallel val users = usersDeferred.await() val posts = postsDeferred.await() _dashboard.value = Dashboard(users, posts) _loading.value = false }

async vs launch comparison

Kotlin — Sequential vs parallel// Sequential — total time = time1 + time2 viewModelScope.launch { val users = api.getUsers() // waits val posts = api.getPosts() // waits after users } // Parallel — total time ≈ max(time1, time2) viewModelScope.launch { coroutineScope { val users = async { api.getUsers() } val posts = async { api.getPosts() } _data.value = CombinedData(users.await(), posts.await()) } }

coroutineScope and supervisorScope

Kotlin — Structured concurrency// coroutineScope — if one child fails, all siblings are cancelled suspend fun loadAllData(): Pair<List<User>, List<Post>> = coroutineScope { val users = async { api.getUsers() } val posts = async { api.getPosts() } Pair(users.await(), posts.await()) } // supervisorScope — one child failure doesn't cancel siblings suspend fun loadDashboardSafely() = supervisorScope { val users = async { runCatching { api.getUsers() }.getOrDefault(emptyList()) } val posts = async { runCatching { api.getPosts() }.getOrDefault(emptyList()) } Dashboard(users.await(), posts.await()) }

Replacing AsyncTask

AsyncTask (deprecated)Coroutines equivalent
onPreExecute()Code before withContext(IO)
doInBackground()withContext(Dispatchers.IO) { ... }
onPostExecute()Code after withContext on Main
cancel()job.cancel() or scope cancellation

5Background Processing

Coroutines handle in-app background work tied to a lifecycle. For work that must survive app closure — sync, uploads, periodic tasks — combine coroutines with WorkManager or run them inside a Service.

ViewModel background fetch pattern

Kotlin — Complete ViewModel patternclass TaskViewModel( private val repository: TaskRepository = TaskRepository() ) : ViewModel() { private val _uiState = MutableStateFlow<UiState>(UiState.Loading) val uiState: StateFlow<UiState> = _uiState.asStateFlow() init { refreshTasks() } fun refreshTasks() { viewModelScope.launch { _uiState.value = UiState.Loading repository.getTasks() .onSuccess { tasks -> _uiState.value = UiState.Success(tasks) } .onFailure { e -> _uiState.value = UiState.Error(e.message ?: "Unknown error") } } } fun addTask(title: String) { viewModelScope.launch { repository.addTask(Task(title = title)) refreshTasks() } } }

Collect Flow in Fragment

Kotlin — lifecycleScope + repeatOnLifecycleoverride fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState.collect { state -> when (state) { is UiState.Loading -> binding.progressBar.isVisible = true is UiState.Success -> { binding.progressBar.isVisible = false adapter.submitList(state.data) } is UiState.Error -> { binding.progressBar.isVisible = false Toast.makeText(requireContext(), state.message, Toast.LENGTH_SHORT).show() } } } } } }

Coroutine in Service

Kotlin — Service with CoroutineScopeclass SyncService : Service() { private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { serviceScope.launch { try { syncDataWithServer() } catch (e: Exception) { Log.e("SyncService", "Sync failed", e) } finally { stopSelf(startId) } } return START_NOT_STICKY } override fun onDestroy() { serviceScope.cancel() super.onDestroy() } override fun onBind(intent: Intent?): IBinder? = null }

When to use what

ScenarioSolution
API call while screen is openviewModelScope.launch
Database read/write in appwithContext(Dispatchers.IO)
Parallel network requestsasync + await()
Work after app is closedWorkManager (uses coroutines internally)
Long-running foreground taskForeground Service + coroutine scope

6Hands-On Exercises

1

Add coroutine dependencies. Launch a basic coroutine with lifecycleScope.launch and delay().

2

Write a suspend function that simulates a network call. Call it from a ViewModel and update LiveData.

3

Fetch data using withContext(Dispatchers.IO). Confirm UI updates happen on the Main thread.

4

Load users and posts in parallel with async/await. Compare timing vs sequential calls.

5

Build a ViewModel with StateFlow for loading/success/error. Collect it in a Fragment with repeatOnLifecycle.

6

Integrate Retrofit suspend API + Room suspend DAO in a Repository. Call from viewModelScope.

7

Bonus: Rotate the device during a fetch — verify viewModelScope keeps running and UI state is preserved.

Coroutines MCQ Practice

10 Basic MCQs 10 Advanced MCQs

10 Basic Coroutines MCQs

1

Kotlin coroutines provide?

ASQL database
BLightweight async programming
CLayout XML
DBluetooth pairing
Explanation: Sequential-looking code for async work without blocking threads.
2

suspend function?

AJava static method
BRuns only main thread
CCan pause and resume without blocking thread
DDeprecated AsyncTask
Explanation: Marked suspend; callable only from coroutine or suspend fn.
3

launch builder?

ALayout inflate
BReturns value Deferred
CSQL query
DStarts coroutine — returns Job
Explanation: Fire-and-forget; use async for result.
4

Dispatchers.Main?

AUI thread dispatcher
BBackground IO
CDefault CPU pool
DNetwork only
Explanation: Update UI on Main; never block it.
5

Dispatchers.IO?

AUI thread
BOptimized for disk/network I/O
CGPU rendering
DSensor only
Explanation: withContext(Dispatchers.IO) { api.fetch() }
6

viewModelScope?

AGlobalScope replacement
BActivity forever
CCoroutineScope tied to ViewModel lifecycle
DService binder
Explanation: Cancelled when ViewModel onCleared().
7

lifecycleScope?

AGradle scope
BApplication scope
CRoom scope
DScope tied to Activity/Fragment lifecycle
Explanation: Launch UI-related coroutines safe until destroy.
8

async/await pattern?

AParallel work returning Deferred result
BSingle thread only
CDeprecated
DJava synchronized
Explanation: val deferred = async { fetch() }; deferred.await()
9

Coroutine vs Thread?

AOne coroutine one OS thread always
BCoroutines lighter — many on few threads
CThreads lighter
DSame cost
Explanation: Coroutines suspend cooperatively without thread per task.
10

withContext switches?

APermanent thread
BActivity
CDispatcher context for block then returns
DManifest
Explanation: withContext(Dispatchers.IO) { ... } returns to previous context.

10 Advanced Coroutines MCQs

11

SupervisorJob?

ACancels all always
BChild failure doesn't cancel siblings
CMain thread only
DSQL transaction
Explanation: Use in viewModelScope for independent child jobs.
12

CoroutineExceptionHandler?

AJSON parse
BUI layout
CHandles uncaught coroutine exceptions
DGPS
Explanation: launch(handler) { } for global error logging.
13

runBlocking?

AService start
BProduction UI code
CRecommended Activity
DBlocks current thread until coroutine completes — tests mainly
Explanation: Avoid on main thread in production.
14

Flow vs LiveData?

AFlow cold stream with operators; LiveData lifecycle-aware hot
BIdentical
CFlow Android-only legacy
DLiveData coroutine-only
Explanation: Flow for reactive streams; collect with lifecycle.
15

channelFlow vs flow?

ASame
BchannelFlow allows concurrent emit; flow builder sequential
CchannelFlow deprecated
DRoom only
Explanation: Choose based on concurrent production needs.
16

Structured concurrency principle?

ANo hierarchy
BGlobalScope preferred
CCoroutines have scope — cancel scope cancels children
DThreads only
Explanation: Never use GlobalScope in app code.
17

suspendCoroutine?

AManifest
BReplace Retrofit
CUI composable
DBridge callback API to suspend function
Explanation: Convert legacy callback to suspend continuation.
18

delay() in coroutine?

ANon-blocking suspend wait
BThread.sleep on main
CANR cause always
DService kill
Explanation: delay(1000) suspends without blocking thread.
19

Retrofit suspend function?

AMain thread always
BRetrofit calls suspend on background automatically
CRequires AsyncTask
DManual Thread
Explanation: Service interface suspend fun getUsers(): List
20

Test coroutines?

AMain dispatcher only
Bsleep in test
CrunTest + TestDispatcher advanceUntilIdle
DCannot test
Explanation: kotlinx-coroutines-test provides virtual time.
Click an option to select, then check answers.

Coroutines Interview Q&A

15 topic-focused questions for interviews and revision.

1What are coroutines?easy
Answer: Kotlin concurrency primitives for async code that looks sequential — suspend instead of callback hell.
2launch vs async.easy
Answer: launch: Job, no direct result. async: Deferred, await() for result.
3When use Dispatchers.IO vs Default.medium
Answer: IO: network/disk. Default: CPU-intensive work (sorting, parsing large data).
4viewModelScope example.easy
Answer: viewModelScope.launch { val data = repo.load(); _state.value = data }
5Why avoid GlobalScope?medium
Answer: Not tied to lifecycle — leaks and continues after UI destroyed.
6withContext purpose.easy
Answer: Switch dispatcher for block safely — e.g., IO for network then back to Main for UI.
7Convert callback to coroutine.hard
Answer: suspendCoroutine { cont -> api.enqueue(object : Callback { onResponse -> cont.resume(data) }) }
8Handle coroutine exceptions.medium
Answer: try/catch in coroutine, CoroutineExceptionHandler, or supervisorScope.
9Flow collect in Activity.hard
Answer: lifecycleScope.launch { repeatOnLifecycle(STARTED) { viewModel.flow.collect { } } }
10Structured concurrency explained.medium
Answer: Every coroutine belongs to scope; cancellation propagates down tree.
11Retrofit + coroutines pattern.easy
Answer: Repository suspend fun fetch(): repo calls api.getData() directly in viewModelScope.
12Main thread rule.easy
Answer: Never block Main; use suspend/withContext for IO; update UI on Main.
13delay vs Thread.sleep.easy
Answer: delay suspends coroutine without blocking thread; sleep blocks entire thread.
14Testing viewModelScope.hard
Answer: runTest, inject TestDispatcher, advanceUntilIdle, assert state.
15Coroutines vs RxJava on Android.medium
Answer: Coroutines Kotlin-native, simpler with suspend; RxJava powerful operators but heavier — coroutines default for new Kotlin code.