Concurrency and Threading
1. Android Threading Model
Q: What is Android's threading rule?
Never block the main (UI) thread. The main thread:
- Handles all UI rendering and user input
- Blocks for >5 seconds → ANR (Application Not Responding)
- Must be responsive for 60fps (16ms per frame)
All long operations (network, database, heavy computation) must run on background threads.
Q: What are the options for background work?
| Tool | Use Case | Lifecycle |
|---|---|---|
| Coroutines | Most async work | Tied to scope (viewModelScope, lifecycleScope) |
| WorkManager | Guaranteed, deferrable work | Survives process death, respects constraints |
| Foreground Service | Long-running user-visible work | Lives independently of UI |
| Handler/Looper | Legacy, low-level thread communication | Manual management |
Q: Why are coroutines preferred over RxJava or raw threads?
- First-party Kotlin support
- Simpler syntax (sequential code style)
- Structured concurrency prevents leaks
- Less boilerplate than RxJava
- Built-in cancellation
- Better Compose/Flow integration
2. Coroutine Dispatchers
Q: What are the main dispatchers and their characteristics?
| Dispatcher | Thread Pool | Optimized For |
|---|---|---|
| Main | Single main thread | UI updates, light work |
| IO | 64+ threads (elastic) | Blocking I/O (network, disk) |
| Default | CPU core count | CPU-intensive computation |
| Unconfined | Inherits caller | Testing only (dangerous in production) |
Q: How do IO and Default share threads?
They share the same underlying pool but have different limits. IO can scale to many threads (blocking calls don't hurt), while Default is limited to CPU cores (compute-bound work benefits from locality).
Q: What's the difference between withContext and launch?
- withContext: Suspends current coroutine, runs block on specified dispatcher, returns result. For sequential dispatcher switching.
- launch(Dispatcher): Creates new child coroutine on that dispatcher. For parallel/concurrent work.
3. Structured Concurrency
Q: What is structured concurrency?
Coroutines follow a parent-child hierarchy within a scope:
- Parent waits for all children to complete
- Cancelling parent cancels all children
- Child failure propagates to parent (by default)
This prevents orphan coroutines and ensures cleanup.
Q: What scopes should you use in Android?
| Scope | Lifecycle | Use Case |
|---|---|---|
| viewModelScope | ViewModel | Most business logic |
| lifecycleScope | Activity/Fragment | UI-related work |
| rememberCoroutineScope | Composition | Compose event handlers |
| CoroutineScope | Custom | Services, application-level |
Q: Why avoid GlobalScope?
GlobalScope creates coroutines that:
- Never auto-cancel (leak potential)
- Live for app lifetime
- Can't be tested properly
- Violate structured concurrency
Use custom scopes instead, cancelled when appropriate.
4. Exception Handling
Q: How do exceptions propagate in coroutines?
| Builder | Exception Behavior |
|---|---|
| launch | Propagates to parent immediately, cancels siblings |
| async | Stored until await() is called, then thrown |
Q: What is SupervisorJob and when do you use it?
SupervisorJob prevents child failure from canceling siblings. Use when:
- Multiple independent operations shouldn't affect each other
- UI can show partial results even if some fail
- Retry logic for individual failures
viewModelScope uses SupervisorJob by default.
Q: How should you handle exceptions?
try/catch in coroutine → Handle expected errors (network, parsing)
CoroutineExceptionHandler → Global fallback for uncaught exceptions
SupervisorJob → Isolate failures between siblings
For async, always wrap await() in try/catch or use runCatching.
5. Cancellation
Q: How does coroutine cancellation work?
Cancellation is cooperative. Coroutines must check for cancellation:
isActiveproperty- Suspending functions (delay, yield, withContext)
ensureActive()call
If you have a tight loop without suspension, it won't cancel until it checks.
Q: What happens on cancellation?
CancellationException is thrown. It's special:
- Doesn't propagate to parent as failure
- Caught silently by the coroutine machinery
finallyblocks still execute for cleanup
Q: How do you clean up on cancellation?
Use finally block:
try {
doWork()
} finally {
// Always runs, even on cancellation
cleanup()
}
For suspending cleanup, wrap in withContext(NonCancellable).
6. Thread Safety
Q: What thread safety issues occur with coroutines?
Even with single-threaded dispatcher (Main), coroutines can interleave at suspension points. With multi-threaded dispatchers (IO, Default), classic race conditions apply.
Q: How do you achieve thread safety?
| Approach | Use Case |
|---|---|
| Mutex | Protect critical sections (like synchronized) |
| Atomic types | Single variable updates (AtomicInteger, etc.) |
| Single-threaded confinement | Keep state access on one dispatcher |
| Immutable data | Prevent mutation entirely |
| Channel | Thread-safe communication between coroutines |
Q: What's the Actor pattern?
A coroutine that processes messages sequentially from a Channel. All state is confined to the actor, eliminating race conditions. Messages are sent from any thread, processed one at a time.
7. WorkManager
Q: When do you use WorkManager?
For guaranteed background work that must complete even if:
- App is killed
- Device restarts
- Process dies
Examples: syncing data, uploading logs, periodic cleanup.
Q: What are WorkManager constraints?
| Constraint | Purpose |
|---|---|
| NetworkType | Require connected, unmetered, etc. |
| BatteryNotLow | Wait for sufficient battery |
| StorageNotLow | Wait for storage space |
| DeviceIdle | Wait for device idle |
| RequiresCharging | Wait for charging |
Q: OneTimeWorkRequest vs PeriodicWorkRequest?
- OneTimeWorkRequest: Run once, optionally with initial delay
- PeriodicWorkRequest: Repeat at intervals (min 15 minutes)
Q: How do you chain work?
workManager
.beginWith(downloadWork)
.then(processWork)
.then(uploadWork)
.enqueue()
Parallel work uses beginWith(listOf(work1, work2)).
Quick Reference
| Topic | Key Points |
|---|---|
| Threading Rule | Never block main thread; use background threads for I/O and computation |
| Dispatchers | Main (UI), IO (blocking I/O), Default (CPU), Unconfined (testing only) |
| Structured Concurrency | Parent-child hierarchy; automatic cancellation; use viewModelScope/lifecycleScope |
| Exceptions | launch propagates immediately; async stores until await; use SupervisorJob for isolation |
| Cancellation | Cooperative; check isActive or use suspending functions; cleanup in finally |
| Thread Safety | Mutex for critical sections; immutable data; single-threaded confinement |
| WorkManager | Guaranteed execution; survives process death; respects constraints |