Structured Concurrency
📖 Concept
Structured concurrency is the principle that a coroutine's lifetime is bounded by its scope. When a scope is cancelled, all its child coroutines are cancelled. When a child fails, the parent is notified.
Why it matters: Without structured concurrency (like raw threads), you get:
- Leaked threads that run after the component is destroyed
- No automatic cleanup on errors
- Manual lifecycle management for every async operation
The job hierarchy:
viewModelScope.launch (parent Job)
├── async { fetchUser() } (child Job 1)
├── async { fetchPosts() } (child Job 2)
└── launch { logAnalytics() } (child Job 3)
If viewModelScope is cancelled → all three children are cancelled. If child Job 1 fails → siblings 2 and 3 are cancelled (in regular scope), parent is cancelled.
Cancellation propagation rules:
- Parent cancellation → All children cancelled
- Child failure → Parent cancelled → All siblings cancelled (coroutineScope)
- Child failure → Only failed child cancelled, others continue (supervisorScope)
Cooperative cancellation: Coroutines must cooperate by checking isActive or using cancellable functions. delay(), yield(), and all kotlinx.coroutines functions check for cancellation automatically. CPU-bound loops must check manually with ensureActive().
💻 Code Example
1// Structured concurrency in action2class DataSyncManager(3 private val scope: CoroutineScope // Injected scope with lifecycle4) {5 private var syncJob: Job? = null67 fun startSync() {8 syncJob?.cancel() // Cancel previous sync9 syncJob = scope.launch {10 // All children are bounded by this scope1112 // Parallel but dependent — if one fails, cancel all13 coroutineScope {14 launch { syncUsers() }15 launch { syncPosts() }16 launch { syncComments() }17 }18 // Only reaches here if ALL three succeed19 markSyncComplete()20 }21 }2223 fun stopSync() {24 syncJob?.cancel() // Cancels everything cleanly25 }26}2728// Cooperative cancellation — CPU-bound work29suspend fun processLargeList(items: List<Item>) {30 for (item in items) {31 ensureActive() // Check if cancelled before each iteration32 processItem(item) // If scope cancelled, throws CancellationException33 }34}3536// NonCancellable — for cleanup that must complete37suspend fun saveAndCleanup(data: Data) {38 try {39 saveToNetwork(data)40 } finally {41 // Even if cancelled, cleanup must run42 withContext(NonCancellable) {43 saveToLocalDb(data) // Won't be cancelled44 closeConnections()45 }46 }47}4849// Timeout with structured concurrency50suspend fun fetchWithTimeout(): Result<Data> {51 return try {52 withTimeout(5000) { // Cancels after 5 seconds53 val data = api.fetch()54 Result.success(data)55 }56 } catch (e: TimeoutCancellationException) {57 Result.failure(e) // Timeout is a specific CancellationException58 }59}6061// SupervisorScope — independent children62suspend fun loadDashboard(): Dashboard {63 return supervisorScope {64 val weather = async {65 try { api.getWeather() } catch (e: Exception) { null }66 }67 val news = async {68 try { api.getNews() } catch (e: Exception) { emptyList() }69 }70 val stocks = async {71 try { api.getStocks() } catch (e: Exception) { emptyList() }72 }73 // Each can fail independently — others continue74 Dashboard(weather.await(), news.await(), stocks.await())75 }76}
🏋️ Practice Exercise
Practice:
- Explain the job hierarchy and how cancellation propagates through it
- Write a coroutine that processes a large list with cooperative cancellation
- Implement a retry mechanism using structured concurrency (max 3 retries, exponential backoff)
- Explain when to use NonCancellable and why it's important in finally blocks
- Design a data sync system that uses supervisorScope for independent sync operations
⚠️ Common Mistakes
Not checking for cancellation in CPU-bound loops — the coroutine keeps running even after the scope is cancelled
Using try-catch in finally without NonCancellable — suspend functions in finally block are cancelled immediately
Creating unstructured coroutines with GlobalScope in ViewModel — they outlive the ViewModel
Not understanding that delay() checks for cancellation — it throws CancellationException if the coroutine is cancelled
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Structured Concurrency. Login to unlock this feature.