Main takeaway: This curated set covers fundamentals to advanced, with precise explanations, Android-focused patterns, and cross-question answers to help ace interviews and deepen real-world understanding.
Note: Organized by topics. Each item includes the main answer and two cross-questions with answers.
Answer:
- Coroutines are lightweight concurrency primitives that enable asynchronous, non-blocking code. They are not threads; they are executed on threads and can suspend without blocking the thread.
- Key differences:
- Lightweight: thousands of coroutines can be created; threads are expensive.
- Non-blocking suspension: coroutines suspend using suspending functions (e.g., delay), freeing the thread; threads block with Thread.sleep.
- Structured concurrency: coroutine lifecycles can be scoped; raw threads don’t have structured parent-child lifecycles.
- Easier composition: sequential, concurrent, and lazy compositions are first-class.
 
- In Android, coroutines help move long-running I/O and CPU tasks off the main thread to avoid ANRs, while simplifying callback-heavy logic.
Cross Q1: If coroutines are lightweight, why not create unlimited coroutines?
- Answer: Lightweight ≠ free. Excessive coroutines can still overwhelm dispatchers, thread pools, memory (stack frames, closures), and resources (network sockets, DB connections). Always limit concurrency for bounded resources using patterns like Semaphore, chunking, or dispatcher choice.
Cross Q2: What is “cooperative multitasking” in coroutines?
- Answer: Coroutines must reach suspension points or explicitly check cancellation to yield control. Functions like delay/yield/await introduce suspension points; loops should check isActive/ensureActive to remain responsive to cancellation.
Answer:
- Thread.sleep(ms): blocks the entire thread for ms; all work on that thread is paused.
- delay(ms): suspends only the coroutine; the underlying thread is free to run other coroutines; requires a coroutine or another suspending function to call.
- In coroutines, always prefer delay over Thread.sleep to avoid blocking and to maintain responsiveness.
Cross Q1: What happens if delay() is used outside a coroutine?
- Answer: It fails to compile, because delay is a suspending function and must be called from a coroutine or another suspending function. Wrap it with runBlocking or place it inside a coroutine builder like launch/async.
Cross Q2: When would you still prefer Thread.sleep()?
- Answer: In non-coroutine, blocking-only contexts like certain low-level tests or legacy code where blocking is acceptable or suspension cannot be used. In coroutine code, prefer delay.
Answer:
- A suspending function (marked with suspend) can pause execution without blocking the thread and resume later (possibly on a different thread).
- It can be called only from another suspend function or a coroutine.
- Example:
suspend fun fetchData(): Data { delay(1000); return api.get() }Cross Q1: Why can’t a suspending function be called from a regular function? Workaround?
- Answer: Suspending functions need a coroutine context to manage continuation and resumption. Workarounds: wrap calls within a coroutine builder (launch/async) or use runBlocking (in tests/CLI).
Cross Q2: Can a suspending function resume on a different thread? Implications?
- Answer: Yes. After suspension (e.g., delay), resumption may occur on a different thread depending on dispatcher. Implication: avoid thread-local assumptions; use Main dispatcher for UI updates.
Answer:
- The main thread (UI thread) handles input, rendering, and view updates.
- Heavy work on main thread causes jank, freezes, and ANRs (5s+).
- Use Dispatchers.Main for UI, Dispatchers.IO/Default for heavy work; switch back to Main for UI updates using withContext(Dispatchers.Main).
Cross Q1: How to detect heavy main-thread work?
- Answer: StrictMode, Android Studio Profiler (Main Thread timeline), systrace, ANR reports, and logging long main-thread tasks.
Cross Q2: Strategies to move heavy work off main thread?
- Answer: Use coroutines with proper dispatchers, WorkManager for deferrable work, room suspend DAOs, and repository layers returning suspend/Flow with dispatcher switching.
Answer:
- Many operations can run as coroutines on a limited thread pool; suspensions free the thread for other coroutines.
- Compared to spinning up a thread per operation, coroutines multiplex many tasks efficiently, with less memory and context-switch overhead.
Cross Q1: Practical limit of coroutines?
- Answer: No fixed number; depends on device memory and workload. Use sensible concurrency limits (e.g., 4–8 parallel I/O ops), avoid launching unbounded coroutines.
Cross Q2: How do dispatchers manage efficient thread allocation?
- Answer: Dispatchers.Default scales with CPU cores for CPU-bound tasks; Dispatchers.IO uses a larger pool for blocking I/O. The scheduler resumes continuations efficiently without blocking threads unnecessarily.
Answer:
- Builders create/launch coroutines.
- Core builders: launch (fire-and-forget, returns Job), async (returns Deferred), runBlocking (blocks current thread; for tests/CLI).
- Others: withContext (switch context), supervisorScope, produce (channels), etc.
Cross Q1: When choose launch vs async?
- Answer: launch for side-effects without a return value; async for concurrent computations where a result is needed via await.
Cross Q2: Other builders?
- Answer: supervisorScope (children don’t cancel siblings on failure), produce (channels), actor (channel + coroutine), and coroutineScope (structured concurrency that waits for children).
Answer:
- GlobalScope launches coroutines tied to the application process, not a component lifecycle.
- Risks: memory leaks, orphaned work, no automatic cancellation on Activity/Fragment destruction.
- Prefer lifecycle-aware scopes: viewModelScope, lifecycleScope, custom scopes.
Cross Q1: Legitimate GlobalScope use cases?
- Answer: Narrow cases like app-wide telemetry or initializations that truly outlive components. Even then, prefer an application-scoped CoroutineScope injected via DI.
Cross Q2: How to migrate away from GlobalScope?
- Answer: Introduce structured scopes (ViewModelScope/LifecycleScope/Application scope via DI), pass scopes via constructor, and cancel in onCleared/onDestroy as appropriate.
Answer:
- launch returns a Job.
- Job operations: join() to wait, cancel() to request cancellation, isActive/isCancelled/isCompleted for state.
- Use job.cancelAndJoin() for graceful stop and wait.
Cross Q1: join() vs delay() to wait?
- Answer: join awaits completion deterministically; delay blindly waits a time. join is correct for waiting on a specific coroutine.
Cross Q2: Handle exceptions in launch?
- Answer: Use try/catch within the coroutine or supply a CoroutineExceptionHandler in the context. Uncaught exceptions in launch propagate to parent and can be handled by the handler.
Answer:
- launch: returns Job, for side effects, exceptions propagate immediately to parent.
- async: returns Deferred; exceptions are stored and thrown at await(). Use await() or join(); await() gives result or throws.
Cross Q1: What if await() is never called?
- Answer: The async coroutine still runs; if it throws, the exception remains deferred and may be lost without observation. Prefer structured usage where await is always reached or use supervisor patterns.
Cross Q2: Can join() replace await() on Deferred?
- Answer: join() waits without retrieving the result; await() both waits and returns T (and throws if failed). Use await() when you need the value.
Answer:
- runBlocking bridges blocking and suspending worlds by starting a coroutine and blocking the current thread until completion.
- Use in tests, main() of CLI apps, or quick scripts. Avoid in Android UI code, as it blocks the main thread.
Cross Q1: Why not in Android UI?
- Answer: It blocks the main thread, causing freezes/ANRs. Always use non-blocking builders plus Dispatchers.
Cross Q2: Test suspending functions without runBlocking?
- Answer: Use kotlinx-coroutines-test: runTest, TestDispatcher, Turbine for Flow, virtual time control.
Answer:
- Cancellation is not preemptive. Coroutines must check for cancellation at suspension points (delay, yield, await) or explicitly via isActive/ensureActive.
- Non-cooperative loops must be made responsive by adding checks or yields.
Cross Q1: What happens when cancelling a non-cooperative coroutine?
- Answer: It won’t stop promptly; cancellation only takes effect at the next suspension/check. If no checks exist, it may run to completion.
Cross Q2: Make CPU-heavy loop cooperative without delays?
- Answer: Use yield() periodically and/or ensureActive() inside the loop to create cancellation points without adding artificial time delays.
Answer:
- cancel(): requests cancellation; returns immediately.
- join(): waits for completion (whether normal or cancelled).
- cancelAndJoin(): idiomatic combination to cancel then await cleanup/completion.
Cross Q1: Risk of only calling cancel()?
- Answer: The coroutine might still be running; resources may not be cleaned up yet. Use cancelAndJoin to ensure graceful termination.
Cross Q2: When join() without cancel()?
- Answer: When waiting for a running coroutine to finish naturally (e.g., after launching work you expect to complete soon).
Answer:
- yield() is a suspending function that checks for cancellation and allows other coroutines to run. It doesn’t add an artificial delay like delay().
- Useful in tight loops or CPU-bound tasks to maintain responsiveness.
Cross Q1: yield() vs delay(1)
- Answer: yield() performs a cooperative reschedule without sleeping; delay(1) introduces a real timing delay. yield has less time overhead but may still context switch.
Cross Q2: What if CPU-intensive coroutine never yields?
- Answer: It can starve others on the same thread and ignore cancellation until the next suspension, causing responsiveness issues.
Answer:
- CancellationException signals cooperative cancellation; many suspending functions throw it when cancelled.
- Best practice: allow it to propagate; if caught to clean up, rethrow afterwards to honor cancellation.
Cross Q1: CancellationException vs TimeoutCancellationException
- Answer: TimeoutCancellationException is a subtype thrown by withTimeout on timeout; both indicate cancellation, but timeout conveys the cause.
Cross Q2: When handle CancellationException explicitly?
- Answer: When performing cleanup that must run on cancellation (e.g., delete temp files), often inside finally with withContext(NonCancellable). Re-throw after cleanup.
Answer:
- It runs the provided block in a non-cancellable context, typically used in finally to ensure cleanup even if the coroutine was cancelled.
- Example: closing streams, writing checkpoints, committing logs.
Cross Q1: What happens to suspend calls in finally after cancellation without NonCancellable?
- Answer: They may not run or may immediately throw, because the coroutine is cancelled. NonCancellable ensures they complete.
Cross Q2: Risks of NonCancellable?
- Answer: Overuse can delay cancellation response and impact UX. Keep the block minimal and only for essential cleanup.
Answer:
- withTimeout(ms): cancels the block after ms and throws TimeoutCancellationException.
- withTimeoutOrNull(ms): returns null on timeout without throwing.
- Choose based on desired control flow and error semantics.
Cross Q1: Which is better for network calls?
- Answer: withTimeoutOrNull is often simpler for user-facing flows (return null => show retry UI). withTimeout is useful when you want failure to propagate and be handled centrally.
Cross Q2: Implement retry with timeout?
- Answer: Wrap call in withTimeout/withTimeoutOrNull, then use a retry loop with backoff. Example: repeat(times) { try { withTimeout { call() } catch(timeout) { delay(backoff) } }.
Answer:
- Sequential by default: statements execute in order, making reasoning simple.
- Concurrency requires explicit async or multiple launches.
Cross Q1: How to measure sequential vs concurrent performance?
- Answer: Use timestamps/logging, Kotlin measureTimeMillis, or trace tooling to compare total elapsed time with sequential vs async + await compositions.
Cross Q2: Trade-offs of concurrency?
- Answer: Faster completion for independent tasks but increases complexity, error propagation surface, and potential resource contention. Requires careful error handling and limits.
Answer:
- Use async for concurrent starts, then await both results.
val a = async { callA() }
val b = async { callB() }
val result = combine(a.await(), b.await())Cross Q1: If one concurrent task fails?
- Answer: Structured concurrency typically cancels siblings; awaiting a failed Deferred throws. Use supervisorScope to isolate, or handle failures per-task.
Cross Q2: Add timeout to concurrent operations?
- Answer: Wrap awaits in withTimeout, or apply withTimeout to the whole coroutine scope. For per-task timeouts, wrap individual async blocks.
Answer:
- async(start = LAZY) delays starting until first await or start() call. Useful when the result might not be needed or to control start order.
Cross Q1: How does lazy optimize apps?
- Answer: Avoids unnecessary work and resource usage when results are not needed (e.g., user abandons screen).
Cross Q2: Lazy async vs placing async inside if-condition?
- Answer: Functionally similar; lazy defers start until await/start, while conditional creation avoids allocating the coroutine at all. Conditional creation can be cleaner if logic is simple.
Answer:
- Map to async, then awaitAll, but limit parallelism to avoid overload (Semaphore, chunking, dispatcher).
val results = items.map { async { process(it) } }.awaitAll()Cross Q1: Handling failures per item?
- Answer: Wrap per-task try/catch and return Result or null, aggregate successes and errors; don’t let one failure kill all unless desired.
Cross Q2: Deciding concurrency limits?
- Answer: Based on resource type: I/O can be higher (e.g., 8–64), CPU-bound near cores count. Consider server limits, DB connections, and device class.
Answer:
- Scope defines lifetime and parent-child relationships. Cancelling a scope cancels all children.
- Android: use viewModelScope and lifecycleScope to tie to lifecycles; ensures cleanup on clear/destroy.
Cross Q1: What happens to children on parent cancellation?
- Answer: Children are cancelled recursively; cooperative code responds at suspension/check points.
Cross Q2: Create a custom scope?
- Answer: CoroutineScope(context = SupervisorJob() + Dispatcher + optional handler). Cancel it in the appropriate lifecycle method.
Answer:
- Context = set of elements:
- Job: lifecycle/cancellation
- Dispatcher: thread selection
- CoroutineName: debugging
- CoroutineExceptionHandler: global error handling
 
- Child inherits parent context; can override elements.
Cross Q1: Access/modify current context?
- Answer: Use coroutineContext (from Kotlin stdlib) and plus operator to add/override: launch(coroutineContext + CoroutineName("Task")).
Cross Q2: Combining conflicting elements?
- Answer: Last-wins for the same key (e.g., Dispatcher). Be explicit to avoid confusion.
Answer:
- Main: UI thread; for updates, LiveData/StateFlow observation.
- IO: blocking I/O; large pool.
- Default: CPU-bound; pool sized to cores.
- Unconfined: starts in caller thread, may resume anywhere; mainly for testing or specific cases.
Cross Q1: Dispatcher for Room DB?
- Answer: IO. Room’s suspend DAOs already handle threading, but wrapping extra work with IO is appropriate.
Cross Q2: Cost of frequent dispatcher switches?
- Answer: Context switching has overhead; minimize unnecessary withContext calls. Group related work under the same dispatcher.
Answer:
- Confined (e.g., inherited Main/IO): stays on that dispatcher unless changed.
- Unconfined: starts in caller thread, resumes on the thread of the suspension point; unpredictable. Not for production UI logic.
Cross Q1: Why avoid Unconfined in prod?
- Answer: Unpredictable thread after resume can break UI assumptions and introduce race conditions.
Cross Q2: Useful testing scenarios?
- Answer: Quick unit tests needing no dispatcher setup, or verifying behavior independent of a specific thread.
Answer:
- launch: exception is immediately propagated to parent; can be handled by CoroutineExceptionHandler if uncaught.
- async: exception is captured and thrown when await() is called; no handler called until await.
Cross Q1: Structured vs unstructured exception handling?
- Answer: Structured concurrency ties children to parents; failures cancel siblings unless supervised. Unstructured (GlobalScope) risks orphaned failures. Prefer structured for predictability.
Cross Q2: Prevent one failure from crashing all?
- Answer: Use supervisorScope/SupervisorJob to isolate; handle errors locally (try/catch), or convert to Result.
Answer:
- Approaches:
- runTest (kotlinx-coroutines-test) for virtual time, deterministic tests.
- runBlocking for simple cases (non-Android).
- Use TestDispatchers, UnconfinedTestDispatcher.
 
Cross Q1: Advantages of runTest over runBlocking?
- Answer: Virtual time, deterministic scheduling, no real delays, better for time-dependent logic.
Cross Q2: Test time-dependent code with virtual time?
- Answer: Use TestScope and advanceTimeBy/advanceUntilIdle with runTest.
Answer:
- Use structured concurrency, CoroutineExceptionHandler for top-level, try/catch around repository/use cases, SupervisorJob to isolate failures, Result/Either for domain errors.
- Surface errors as UI state.
Cross Q1: Global strategy for network errors?
- Answer: Centralize mapping (e.g., interceptors, error mappers), classify errors (network/auth/server), implement retry/backoff and offline fallbacks, and update UI consistently.
Cross Q2: Result vs throwing exceptions in repositories?
- Answer: Result avoids exceptions as control flow, improves clarity in UI/state layers; exceptions may be fine within repository internals. Choose consistently and document.
Answer:
- viewModelScope (cancels on ViewModel.clear), lifecycleScope with repeatOnLifecycle for UI collection, and custom scopes for managers/services with explicit cancel.
- Avoid GlobalScope; cancel work on lifecycle end.
Cross Q1: What if GlobalScope in Activity that’s recreated?
- Answer: Work continues and may reference destroyed Activity, causing leaks and invalid UI updates.
Cross Q2: Long downloads surviving recreation?
- Answer: Use WorkManager or a foreground service; report progress via persistent storage or LiveData/Flow from repository tied to application scope.
Answer:
- Pick appropriate dispatcher; limit concurrency; avoid unnecessary context switches; cancel unused work; chunk processing; cache results.
- Monitor with Profiler, logs, metrics.
Cross Q1: Benchmark coroutine vs threads?
- Answer: Micro-benchmark specific workloads with JMH (JVM), measureTimeMillis, and Android Profiler. Compare memory/cpu and responsiveness.
Cross Q2: Detect coroutine memory leaks?
- Answer: LeakCanary on Android, heap dumps, tracking long-lived scopes and jobs, ensuring lifecycle cancellation paths.
Answer:
- Use CoroutineName and set “kotlinx.coroutines.debug” system property.
- Strategic logging of thread names, states; use breakpoints in suspend functions; leverage structured scopes for tracing.
Cross Q1: Common coroutine bugs?
- Answer: Missing cancellation, using Main for heavy work, lost exceptions in async (never awaited), leaking scopes, and incorrect dispatcher usage.
Cross Q2: Debug stuck coroutine?
- Answer: Thread dump to check blocked threads, inspect job states, ensureActive checks, verify deadlocks and dispatcher starvation, add timeouts.
Answer:
- Implement retry with exponential backoff and exception filtering (retry IOExceptions only), using delay/backoff between tries. Wrap in withTimeout if needed.
Cross Q1: Retry only specific exceptions?
- Answer: Filter in catch or retryWhen (for Flow); e.g., retry on IOException or HTTP 5xx, but not on 4xx/validation errors.
Cross Q2: Add network availability checks?
- Answer: Before retry, check connectivity via ConnectivityManager/NetworkCallback; suspend until network becomes available or give up with a user-friendly error.
Answer:
- Use IO dispatcher for downloading/decoding, memory cache (LruCache), cancel previous ImageView job (tag with Job) for RecyclerView reuse, and update UI on Main.
Cross Q1: Add disk cache?
- Answer: After network, write to disk (IO), read from disk cache first; use hashed filenames; coordinate cache eviction policies.
Cross Q2: Not cancelling previous jobs in RecyclerView?
- Answer: Image “flashing,” wrong images in recycled cells, wasted network/CPU, and memory churn. Always cancel stale jobs.
Answer:
- Use suspend DAOs for queries/inserts; return Flow for reactive streams; use Dispatchers.IO when doing extra work.
Cross Q1: Why suspend DAOs?
- Answer: To avoid main-thread blocking; Room handles threading and transactions efficiently with suspend functions.
Cross Q2: Database transactions with coroutines?
- Answer: Use @Transaction on DAO methods with suspend functions, or database.withTransaction { suspend block }.
Answer:
- Use Channel/Flow, debounce(), distinctUntilChanged(), and flatMapLatest for new queries. Handle empty query separately and show loading/errors in UI state.
Cross Q1: Add loading/error states?
- Answer: Model sealed UI states; emit Loading before search, catch and map exceptions to Error, emit Success on results.
Cross Q2: Offline vs online search?
- Answer: Offline: local DB queries with Flow and dispatchers; Online: debounce + repository + cache/memory. For hybrid, combine local immediate results with remote updates.
Answer:
- Loop with while(isActive), performSync inside try/catch; handle CancellationException distinctly; delay between iterations; consider WorkManager for OS-coordinated periodic work.
Cross Q1: Backoff for failures?
- Answer: Increase delay exponentially after failures; cap max delay; reset on success.
Cross Q2: Survive app termination?
- Answer: Use WorkManager periodic work with constraints; foreground service for user-visible ongoing sync.
Answer:
- Sequential when dependencies exist; parallelize independent parts using async and then combine; handle errors carefully and cancel siblings if needed.
Cross Q1: If a dependency fails?
- Answer: Fail fast, surface error; optionally fallback to cache; cancel other ongoing coroutines if result is unusable.
Cross Q2: Calls based on multiple prior results?
- Answer: Use coroutineScope and await step-by-step; for complex graphs, create dedicated use-cases to express data dependencies clearly.
Answer:
- Stream with IO dispatcher, report progress on Main, ensureActive checks, delete partial file on cancel/error; handle large buffer sizes and content-length unknowns.
Cross Q1: Pause/resume?
- Answer: Support HTTP range requests, store current bytes, resume from offset; persist state in DB; require server support.
Cross Q2: Handle network interruptions?
- Answer: Catch IOExceptions, retry with backoff, resume if range supported; else restart.
Answer:
- Track last sync time; fetch deltas; resolve conflicts (LWW, merge, or prompt); apply changes atomically; push local changes; update sync timestamp.
Cross Q1: Conflict resolution both sides modified?
- Answer: Define strategy per domain: LWW with timestamps, field-level merges, CRDTs, or user-driven conflict resolution UI.
Cross Q2: Optimize for large datasets?
- Answer: Pagination/chunking, hashing/checksums, server-side diff APIs, batched writes, compression, and indexing.
Answer:
- Repositories return suspend/Flow, switch dispatchers appropriately, cache locally, fallback when offline, map errors into domain types, and ensure structured lifecycle.
Cross Q1: Cache invalidation strategies?
- Answer: Time-to-live, versioning, ETags, last-modified checks, manual invalidation triggers, and conditional GET.
Cross Q2: Handling auth tokens?
- Answer: Use an OkHttp interceptor to refresh tokens with a mutex to avoid stampede; retry the original request after refresh.
Answer:
- Use flow/flowOn for producers, map/filter/catch/flatMapLatest for transforms, stateIn/shareIn for sharing state. Combine multiple sources and manage lifecycle with viewModelScope.
Cross Q1: flow vs channelFlow vs callbackFlow?
- Answer:
- flow: cold, sequential builder.
- callbackFlow: bridge callback-based APIs with safe channel and awaitClose.
- channelFlow: similar to callbackFlow but supports concurrent emissions.
 
- Choose based on source (callbacks vs sequential).
Cross Q2: Backpressure handling?
- Answer: Use buffer(), conflate(), sample(), throttle/debounce, and apply proper onBackpressure strategies depending on use case.
Answer:
- Map low-level exceptions to domain-specific AppException. Use sealed results (Success/Error). Present user-friendly messages in UI. Keep retry/backoff at appropriate layers.
Cross Q1: Retry strategies per error?
- Answer: Retry IOExceptions/5xx with exponential backoff; don’t retry 4xx (client errors); re-authenticate on 401; immediate fail for validation errors.
Cross Q2: Logging and crash reporting?
- Answer: Centralized error logger; attach context (user, device, network); report non-fatal to monitoring; redact sensitive data.
Answer:
- Use ViewModel + viewModelScope to survive rotations; use repeatOnLifecycle to collect Flows only when active. Avoid storing Activity references in long-lived coroutines.
Cross Q1: Pros/cons of approaches?
- Answer:
- ViewModel: recommended, easy lifecycle management.
- lifecycleScope + repeatOnLifecycle: great for UI collection.
- Retained fragments: legacy; more boilerplate.
 
- Prefer ViewModel.
Cross Q2: Show progress across rotation?
- Answer: Store progress in ViewModel state (LiveData/StateFlow); UI re-subscribes after recreation and renders current progress.
Answer:
- Use ensureActive/isActive checks inside loops; yield periodically; handle cleanup in finally with NonCancellable for critical sections; design I/O loops to check cancellation.
Cross Q1: ensureActive() vs isActive?
- Answer: ensureActive throws CancellationException immediately if cancelled; isActive returns Boolean, you must decide how to handle it (e.g., break/throw).
Cross Q2: Design resumable file processing?
- Answer: Process in chunks, persist checkpoints (offsets), on resume continue from last checkpoint; handle partial outputs safely.
Answer:
- Dispatcher choice (IO vs Default), limit concurrency, batch work, reduce context switches, cancel unused jobs, cold flows with WhileSubscribed, and caching.
Cross Q1: Measure/monitor in production?
- Answer: Custom metrics (timers, counters), Firebase Performance, logs with timings, traces, ANR monitoring, and memory profiling.
Cross Q2: Memory implications of many inactive coroutines?
- Answer: Each holds stack frames/closures/captures; if retained by long-lived scopes, memory grows. Cancel or avoid retaining jobs; prefer cold Flows when possible.
Answer:
- Combine multiple flows, switch streams dynamically with flatMapLatest, debounce/throttle, retry with conditions, cache with stateIn/shareIn, and distinctUntilChanged to prevent redundant UI updates.
Cross Q1: Auto-switch between online/offline sources?
- Answer: Combine network status Flow with data Flow; use flatMapLatest to choose remote or local source based on connectivity; cache remote results to local.
Cross Q2: Prevent leaks with complex flows?
- Answer: Scope with viewModelScope; use WhileSubscribed; avoid GlobalScope; ensure collectors cancel on lifecycle; avoid retaining contexts in lambdas.
Answer:
- Use suspendCancellableCoroutine to wrap async callbacks and support cancellation. For streams, use callbackFlow and awaitClose for cleanup.
Cross Q1: suspendCoroutine vs suspendCancellableCoroutine?
- Answer: suspendCancellableCoroutine supports cancellation (invokeOnCancellation, cancel underlying work); suspendCoroutine does not. Prefer the cancellable variant for most async integrations.
Cross Q2: Handle timeouts when wrapping callbacks?
- Answer: Wrap suspend call in withTimeout/withTimeoutOrNull, and ensure underlying operation is also cancelled on timeout via invokeOnCancellation.
Answer:
- Use runTest and TestDispatchers, set Main dispatcher in tests, test Flows with Turbine, control virtual time, isolate side effects, and use fake repositories.
Cross Q1: Test different dispatchers without slow tests?
- Answer: Inject CoroutineDispatchers via interfaces; in tests, provide TestDispatcher/UnconfinedTestDispatcher to avoid real threads and delays.
Cross Q2: Test cancellation behavior?
- Answer: Launch test coroutines, trigger cancel(), assert cleanup side effects, verify job states, and use Turbine’s cancelAndConsumeRemainingEvents for Flow.
Answer:
- Avoid GlobalScope in UI; use lifecycle-aware scopes; don’t hold Activity/Fragment refs in long-running jobs; manage hot flows with WhileSubscribed; cancel jobs on lifecycle end.
Cross Q1: Tools for leak detection?
- Answer: LeakCanary, Android Profiler, heap dumps, allocation tracking, and strict reviews of scope lifetimes.
Cross Q2: Automatic cleanup on cancellation?
- Answer: Use try/finally with withContext(NonCancellable) for critical cleanup; register invokeOnCompletion on Job; close channels/streams in awaitClose for callbackFlow.
Answer:
- Define application-, feature-, and component-scoped CoroutineScopes (DI-provided), use SupervisorJob to isolate child failures, centralize exception handling, and provide cancellation APIs.
Cross Q1: Plugin-based feature architecture?
- Answer: Provide each plugin a supervised scope from an application parent; expose APIs for plugin lifecycle start/stop; isolate failures with SupervisorJob and dedicated handlers.
Cross Q2: Coordinate across multi-module projects?
- Answer: Define dispatcher providers and scopes in a core module; pass scopes via DI; define contracts (suspend/Flow) between modules; avoid leaking implementation details.
Answer:
- For deferrable, guaranteed execution: WorkManager + CoroutineWorker + constraints + backoff + progress.
- For user-visible long-running: Foreground service with notification and a dedicated scope.
- Ensure cancellation-aware code, progress reporting, and retries.
Cross Q1: Balance Foreground Service vs WorkManager?
- Answer:
- Foreground service: ongoing user-visible tasks (media, navigation, large file transfer).
- WorkManager: deferrable, guaranteed even after reboot, obeying OS constraints.
- Choose based on visibility, guarantees, and OS policies.
 
Cross Q2: Handle different device capabilities?
- Answer: Adaptive concurrency based on cores/memory, batch processing, dynamic backoff, and constraints (battery, network type) configured via WorkManager.
- Prefer structured concurrency (scopes tied to lifecycle).
- Use the correct dispatcher per workload; avoid blocking Main.
- delay vs sleep: suspend vs block.
- launch (Job) vs async (Deferred + await).
- Handle cancellation cooperatively; cleanup with NonCancellable.
- Use withTimeout/withTimeoutOrNull based on semantics.
- Test with runTest and virtual time; inject dispatchers.
- Avoid GlobalScope; prevent leaks with proper scope management.
- Use Flow for reactive pipelines; manage lifecycle with repeatOnLifecycle/stateIn.
This markdown is crafted for Android interviews and day-to-day development, ensuring deep understanding and practical readiness.