Start with the dependency graph. If loading exercises needs the course ID returned by another call, those operations are sequential. If the course summary and bookmarks are independent, they can overlap.
The following application-specific calls are assumed to be main-safe suspending functions:
suspend fun loadCourse(id: String): CourseScreen = coroutineScope {
val summary = async { repository.loadSummary(id) }
val bookmarks = async { repository.loadBookmarks(id) }
CourseScreen(summary.await(), bookmarks.await())
}
Start both children before awaiting either. Writing async { first() }.await() followed by async { second() }.await() still serializes those operations.
The ordinary scope treats both results as required: a child failure cancels its sibling. If one result is optional, design a separate recovery policy. Avoid launching detached work just to make a screen appear faster; it creates unclear cancellation and error ownership.
Concurrency means overlapping lifetimes. On Main, independent suspending requests can overlap their waits without executing Kotlin code simultaneously. Actual CPU parallelism depends on the dispatcher and available threads. async alone does not move CPU work off Main.
Does join() return a result or rethrow a target job's failure? No. It waits for completion. await() retrieves a Deferred's value or throws its failure. Parent cancellation can still make a caller's join() throw cancellation, so inspect the whole job hierarchy.
Mark this when you can explain the answer in your own words.
Help fellow developers prepare for interviews
Sharing helps the Android community grow ๐