# Interview Prep 1

### Kotlin & Language

**Q1.** What are the key differences between `inline`, `noinline`, and `crossinline` functions in Kotlin?

`inline` copies the function body at the call site, eliminating lambda allocation overhead and allowing non-local returns. Use it for higher-order functions with lambda parameters to reduce runtime cost. `noinline` marks specific lambda parameters of an inline function that should not be inlined, keeping them as regular function objects for storage or repeated use. `crossinline` forbids non-local returns inside the lambda, ensuring the inlined code doesn't return from an enclosing function; useful when the lambda is executed in a different execution context like a coroutine or callback. All three control how lambdas behave at the bytecode level. `inline` is powerful but increases code size if overused. `noinline` sacrifices performance for flexibility. `crossinline` enforces safety when passing lambdas to nested scopes. Choose based on performance needs and control flow requirements.

```kotlin
inline fun measure(crossinline action: () -> Unit, noinline callback: () -> Unit) {
    val start = System.currentTimeMillis()
    action() // can't return from here
    println("Took ${System.currentTimeMillis() - start}ms")
    callback() // stored/called later
}
```

Read more: <https://kotlinlang.org/docs/inline-functions.html>

***

**Q2.** How do coroutines differ from RxJava in terms of cancellation, backpressure, and thread management?

Coroutines use structured concurrency where cancellation propagates automatically through parent-child hierarchies via `Job` trees. RxJava requires explicit `Disposable` management and composite disposables for cleanup. Backpressure in RxJava is explicit via strategies like `onBackpressureBuffer` or `drop`; Kotlin Flow handles it naturally through suspending emission—consumers pull at their own pace since collectors suspend until ready. Thread management in coroutines is declarative via `Dispatchers` (Main, IO, Default) and can be switched within the same function using `withContext`. RxJava uses `subscribeOn` and `observeOn` schedulers, which are more rigid and harder to follow. Coroutines are lighter, using fewer objects per operation. RxJava offers richer operator ecosystems but steeper learning curves. Both can interoperate via `kotlinx-coroutines-rx3` adapters.

```kotlin
viewModelScope.launch {
    val data = withContext(Dispatchers.IO) { repository.fetch() }
    _state.value = data
} // auto-cancelled when ViewModel clears
```

Read more: <https://kotlinlang.org/docs/coroutines-basics.html>

***

**Q3.** Explain the difference between `Flow` and `LiveData`—when would you choose one over the other?

`Flow` is a cold stream built on coroutines that emits values sequentially and supports complex transformations via functional operators like `map`, `filter`, and `flatMapLatest`. `LiveData` is an observable data holder class aware of Android lifecycle, automatically stopping observation when the lifecycle owner is inactive. Use `Flow` for data layer and business logic where you need backpressure, threading control, and composable async streams. Use `LiveData` in the presentation layer when you need lifecycle-aware UI updates without manual subscription management. `StateFlow` and `SharedFlow` are Flow variants that can replace `LiveData` in ViewModels while offering more configuration. `LiveData` is simpler for basic UI cases but lacks operators and coroutine integration. Modern Android recommends `Flow` in repositories and `LiveData` or `StateFlow` in ViewModels depending on team conventions.

```kotlin
class Repo {
    fun getItems(): Flow<List<Item>> = dao.observeAll()
        .map { it.filter { item -> item.active } }
        .flowOn(Dispatchers.Default)
}
```

Read more: <https://developer.android.com/kotlin/flow>

***

**Q4.** What are reified type parameters and what limitations do they solve?

Reified type parameters, declared with `inline fun <reified T>`, make generic type information available at runtime inside the function body. Normally, JVM type erasure removes generic types after compilation, so you cannot check `if (x is T)` or access `T::class` in regular generic functions. `reified` solves this by inlining the function, allowing the compiler to substitute the actual type argument at each call site. This enables type checks, reflection, and instantiation patterns without passing `Class<T>` tokens manually. Common uses include JSON deserialization (`fromJson<T>()`), ViewModel creation by class, and type-safe routing. The limitation is that `reified` only works with `inline` functions, increasing code size. It cannot be used with anonymous objects or non-inline contexts.

```kotlin
inline fun <reified T> String.fromJson(): T {
    return Json.decodeFromString(serializer(), this)
}
val user: User = jsonString.fromJson<User>()
```

Read more: <https://kotlinlang.org/docs/inline-functions.html#reified-type-parameters>

***

**Q5.** How does Kotlin's delegation pattern (`by`) work under the hood, and where have you used it in production?

The `by` keyword implements the delegation pattern, generating a hidden property that stores the delegate instance and forwarding all interface methods to it. For `by lazy`, Kotlin creates a `Lazy` instance that synchronizes initialization on first access. For class delegation `class MyList by ArrayList<String>()`, the compiler generates forwarding methods for every interface member to the delegate property. In production, use `by lazy` for expensive ViewModel or repository initialization. Use class delegation to add behavior to existing classes without inheritance—e.g., decorating a `Repository` with caching or logging. Use `by viewModels()` and `by activityViewModels()` for Hilt ViewModel injection. The generated bytecode is efficient but increases class method count. It enforces composition over inheritance.

```kotlin
class CachedRepo(
    private val delegate: Repo
) : Repo by delegate {
    private val cache = mutableMapOf<String, Data>()
    override fun get(id: String): Data = cache.getOrPut(id) { delegate.get(id) }
}
```

Read more: <https://kotlinlang.org/docs/delegation.html>

***

**Q6.** What are the risks of using `suspend` functions inside non-suspending lambdas?

Passing a suspend function to a regular lambda (e.g., `setOnClickListener { mySuspendFun() }`) requires wrapping it in a coroutine builder like `lifecycleScope.launch`, which creates a new coroutine with potentially uncontrolled scope. This risks structured concurrency violations—if the lambda outlives the UI component, the coroutine may leak or access destroyed views. Without proper scope, exceptions propagate differently and cancellation is manual rather than automatic. Fire-and-forget patterns in callbacks bypass parent job hierarchies, making cleanup difficult. Nested suspend calls inside non-suspending contexts often lead to callback hell if not properly structured. Always ensure the coroutine builder uses a scope tied to the lifecycle. Using `suspendCoroutine` or `callbackFlow` is safer for bridging callbacks to coroutines.

```kotlin
button.setOnClickListener {
    lifecycleScope.launch {
        viewModel.save() // scoped to lifecycle
    }
}
// Never: GlobalScope.launch { ... }
```

Read more: <https://kotlinlang.org/docs/coroutines-basics.html>

***

**Q7.** How do you handle structured concurrency when launching coroutines in custom `ViewModel` scopes?

Extend `ViewModel` and use its built-in `viewModelScope`, which follows the ViewModel lifecycle and cancels automatically on `onCleared()`. For custom scopes, implement `CoroutineScope` with a `SupervisorJob` plus a dispatcher, then cancel it manually in the lifecycle teardown. Use `supervisorScope` or `coroutineScope` builders inside ViewModel methods to enforce parent-child relationships where child failures don't cancel siblings. Launch work using `launch` or `async` within these scopes, never with GlobalScope. For shared operations across multiple ViewModels, delegate to a UseCase or Repository scoped to a higher-level component. Always handle exceptions with `CoroutineExceptionHandler` attached to the scope. Test by injecting `TestDispatcher` and verifying cancellation behavior.

```kotlin
class MyViewModel : ViewModel() {
    init {
        viewModelScope.launch(SupervisorJob() + Dispatchers.IO) {
            // cancelled automatically in onCleared()
        }
    }
}
```

Read more: <https://developer.android.com/topic/libraries/architecture/coroutines>

***

**Q8.** Explain the difference between `SharedFlow` and `StateFlow` with respect to configuration and replay behavior.

`StateFlow` is a conflated `SharedFlow` specialized for state representation—it always holds a single current value, emits only when the value changes (`distinctUntilChanged` behavior), and requires an initial state. `SharedFlow` is more configurable: you set `replay` cache size, `extraBufferCapacity`, and `onBufferOverflow` strategy (DROP\_OLDEST, DROP\_LATEST, SUSPEND). `StateFlow` has no replay buffer beyond the current state and never suspends emitters. `SharedFlow` can replay multiple past values to new subscribers, making it ideal for events like navigation or snackbars. `StateFlow` is read synchronously via `.value`, while `SharedFlow` is only observed. Both are hot flows—active regardless of collectors. Configure `SharedFlow` carefully to avoid memory leaks from large replay caches.

```kotlin
val events = MutableSharedFlow<String>(replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
val state = MutableStateFlow(UiState.Loading)
```

Read more: <https://kotlinlang.org/docs/flow.html#stateflow-and-sharedflow>

***

**Q9.** What is the purpose of `sealed` classes versus `sealed` interfaces in Kotlin, and how do they affect exhaustive `when` expressions?

`sealed` classes restrict inheritance to a known set of subclasses declared in the same package or module, enabling exhaustive `when` expressions without an `else` branch. `sealed` interfaces (Kotlin 1.5+) extend this to interfaces, allowing a type to have multiple sealed supertypes and enabling more flexible composable hierarchies. Both guarantee at compile time that all possible implementations are handled, improving type safety and enabling sealed class serialization. `sealed` classes can hold state and have constructors; `sealed` interfaces cannot hold state but support multiple inheritance. In `when` expressions, the compiler checks exhaustiveness for both, but sealed interfaces require all implementing classes to be visible. Use sealed classes for closed state hierarchies (Result, UiState). Use sealed interfaces for defining closed capability contracts across unrelated types.

```kotlin
sealed interface Result
sealed class NetworkResult : Result {
    data class Success(val data: String) : NetworkResult()
    data class Error(val e: Throwable) : NetworkResult()
}
fun handle(r: NetworkResult) = when (r) { is Success -> ... is Error -> ... }
```

Read more: <https://kotlinlang.org/docs/sealed-classes.html>

**Q10.** How do you prevent memory leaks when using coroutines in custom views or long-lived components?

Never use `GlobalScope`; always tie coroutines to a lifecycle-bound scope like `LifecycleScope` or a custom scope cancelled in `onDetachedFromWindow()`. For custom views, store a `Job` or `CoroutineScope` field and cancel it when the view detaches. Use `LifecycleOwner` extensions to auto-cancel. Avoid capturing strong references to the view in coroutine lambdas; use `WeakReference` or check `isAttachedToWindow` before updating UI. For `Flow` collection, use `flowWithLifecycle` or `repeatOnLifecycle` to pause collection when the lifecycle is inactive. In ViewModels, rely on `viewModelScope`. Use `callbackFlow` with `awaitClose` for callback-based APIs to ensure cleanup. Profile with LeakCanary and Android Studio Memory Profiler to detect retained coroutine contexts. Prefer `suspendCancellableCoroutine` for cancellable bridge code.

```kotlin
class MyView @JvmOverloads constructor(...) : View(...) {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        scope.cancel()
    }
}
```

Read more: <https://developer.android.com/topic/libraries/architecture/coroutines>

***

### Android Architecture & Design Patterns

**Q11.** Describe the evolution of your app's architecture from MVP to MVVM to MVI—what drove each transition?

MVP separated presentation logic via interfaces but created verbose boilerplate and tight coupling between Presenters and Views through contract interfaces. MVVM replaced this with data binding and `ViewModel` from AAC, surviving configuration changes and reducing manual view updates via `LiveData`/`StateFlow`. The transition was driven by Google’s AAC recommendations, elimination of presenter lifecycle management, and easier testing through observable state. MVI added unidirectional data flow and immutable state objects, solving the problem of scattered state mutations in complex screens where multiple `LiveData` sources caused inconsistent UI. We adopted MVI for screens with complex user interactions, time-travel debugging, and predictable state reduction. Each step reduced framework-specific code and increased testability. The driver was always reducing bugs from state inconsistency and improving developer velocity.

```kotlin
// MVI: Single state, single event entry
data class State(val items: List<Item> = emptyList(), val loading: Boolean = false)
class VM : ViewModel() {
    private val _state = MutableStateFlow(State())
    val state = _state.asStateFlow()
    fun onEvent(e: Event) { /* reduce to new state */ }
}
```

Read more: <https://developer.android.com/topic/architecture>

**Q12.** How do you enforce unidirectional data flow in a large-scale app with multiple feature modules?&#x20;

Define a single `State` data class per screen representing the entire UI snapshot. Expose only one observable state stream (`StateFlow<<State>`) from the ViewModel. All user actions flow through a single `onEvent()` method dispatching to processors. Use a reducer pattern: `(State, Event) -> State` to compute new states immutably. Prevent views from mutating state directly—only emit events. Share common state via a centralized state holder or mediator if cross-feature, but keep screen states local to avoid tight coupling. Use Kotlin's `copy()` for immutable updates. Enforce via lint rules banning public mutable state properties in ViewModels. Document the pattern in architecture guidelines. Review PRs for state mutations outside reducers. Modularize by feature with each module owning its state, event, and reducer contracts.

```kotlin
fun reduce(state: State, event: Event): State = when (event) {
    is Refresh -> state.copy(loading = true)
    is DataLoaded -> state.copy(items = event.data, loading = false)
}
```

Read more: <https://developer.android.com/topic/architecture/ui-layer/events>

**Q13.** What is your approach to designing a clean architecture boundary between domain and data layers?

The domain layer contains pure Kotlin UseCases and Repository interfaces with no Android dependencies, making it testable with JVM unit tests. The data layer implements repositories using Room, Retrofit, or local data sources, mapping DTOs/entities to domain models. Define Repository interfaces in domain; implement them in data. Use dependency inversion so domain never imports data layer classes. Models crossing the boundary should be immutable data classes. Use mappers (not extension functions on domain models) to convert between network/DB models and domain models to avoid leaking serialization logic. Keep domain logic framework-agnostic—no `Context`, no `ViewModel`, no coroutine dispatchers. Inject dispatchers into data layer implementations, not domain. This boundary ensures business rules survive framework changes and can be reused across platforms.

```kotlin
// domain
interface UserRepo { suspend fun getUser(id: String): User }
// data
class UserRepoImpl @Inject constructor(private val api: Api, private val dao: UserDao) : UserRepo {
    override suspend fun getUser(id: String) = api.getUser(id).toDomain().also { dao.save(it.toEntity()) }
}
```

Read more: <https://developer.android.com/topic/architecture/domain-layer>

**Q14.** How do you handle navigation in a multi-module app—do you prefer the Navigation Component, custom routers, or something else?

Use the Navigation Component with a single activity architecture and feature-module graphs. Each feature module defines its own `navigation.xml` graph with deep links, while the app module hosts the root `NavHostFragment` and handles cross-feature navigation via deep link URIs or a shared navigation interface. This decouples features—they don't depend on each other's fragments directly. For complex conditional navigation (A/B flows, auth gates), wrap Navigation Component in a `Navigator` interface defined in a core module, implemented using the component internally. Avoid custom routers unless you need transitions unsupported by the component or shared element animations across modules. Use type-safe navigation with Kotlin DSL or Safe Args for compile-time route verification. Test navigation with `FragmentScenario` and Espresso.

```xml
<!-- feature_home/nav_graph.xml -->
<<navigation>
    <deepLink app:uri="myapp://home/dashboard" />
    <fragment android:id="@+id/dashboard" android:name="com.example.home.DashboardFragment" />
</navigation>
```

Read more: <https://developer.android.com/guide/navigation>

**Q15.** What are the trade-offs of using a monolithic `ViewModel` versus splitting state into multiple smaller `ViewModel`s?

A monolithic ViewModel centralizes state and logic for a screen, reducing inter-ViewModel communication overhead and making the full UI state visible in one place. However, it grows unwieldy for complex screens, violates single responsibility, and complicates testing. Multiple smaller ViewModels scoped to sub-screens or components improve separation of concerns and allow independent testing, but require event delegation or a shared state owner to coordinate. They increase lifecycle complexity if not aligned with the host lifecycle. For Compose, prefer smaller ViewModels tied to specific screen regions or flows, using a shared parent ViewModel for cross-cutting state. In Fragments, monolithic is often simpler due to lifecycle coupling. The trade-off is cohesion versus maintainability—split when the ViewModel exceeds \~500 lines or handles unrelated domains.

```kotlin
class ParentViewModel @Inject constructor() : ViewModel() {
    val headerVm = HeaderViewModel()
    val listVm = ListViewModel()
    // coordinate via shared state or events
}
```

Read more: <https://developer.android.com/topic/libraries/architecture/viewmodel>

**Q16.** How do you manage shared state across features without creating tight coupling?

Introduce a shared core module containing state interfaces and lightweight data classes that all features depend on. Implement the state holder in a dedicated module or the app module, injecting it via DI. Use reactive streams (`StateFlow`, `BroadcastChannel`) for state distribution so consumers observe without knowing the producer. Avoid direct feature-to-feature imports; route communication through the app module or a mediator using deep links or a navigation interface. For global user state (auth, profile), use a `UserSession` repository in core that features observe. Never let Feature A directly call Feature B's ViewModel. If features must react to each other, publish domain events to a lightweight event bus scoped to the app. Keep shared state minimal—most state should be feature-local. Modular architecture with clear API boundaries prevents coupling.

```kotlin
// core module
interface AuthStateProvider { val isLoggedIn: StateFlow<Boolean> }
// app module
class AuthStateProviderImpl @Inject constructor(repo: AuthRepo) : AuthStateProvider {
    override val isLoggedIn = repo.observeSession().map { it != null }.stateIn(scope, SharingStarted.Eagerly, false)
}
```

Read more: <https://developer.android.com/topic/modularization>

**Q17.** Explain how you would architect a feature that needs to work both online and offline with eventual consistency.

Use a local database (Room) as the single source of truth for the UI. The repository checks connectivity: if online, fetch remote data, map to local entities, persist, then expose local data via `Flow`. If offline, expose cached data immediately. Write operations go to local DB first for instant UI feedback, then sync to remote via a background WorkManager task queue. Handle conflicts with timestamps, version vectors, or server-wins/last-write-wins strategy depending on business rules. Expose sync status (pending, synced, error) through the repository so UI can show indicators. Use exponential backoff for retry. Implement a sync engine that batches changes to reduce API calls. Ensure idempotency on the server for retried requests. Test with network link conditioner and airplane mode toggling.

```kotlin
class NoteRepo @Inject constructor(private val api: NoteApi, private val dao: NoteDao) {
    fun observeNotes(): Flow<List<<Note>> = dao.observeAll()
    suspend fun addNote(note: Note) {
        dao.insert(note.copy(syncStatus = PENDING))
        SyncWorker.enqueue(note.id)
    }
}
```

Read more: <https://developer.android.com/topic/architecture/data-layer>

**Q18.** What strategies do you use to prevent `ViewModel` bloat when a screen has complex business logic?

Delegate business logic to UseCases in the domain layer, keeping ViewModels as thin orchestrators that only map UseCase outputs to UI state. Group related operations into cohesive UseCases rather than one per action. Use helper classes or state machines for complex validation or form logic. Extract Compose UI state calculation into state holders (`remember` or dedicated classes) when not business logic. Avoid putting navigation logic, resource strings, or `Context` references in ViewModels. If a screen has multiple independent regions (e.g., header, list, footer), split into nested ViewModels or state holders. Move data formatting (dates, currencies) to UI mappers or `StringRes` providers injected into ViewModels. Enforce a maximum line count per ViewModel in code review. Refactor proactively when cyclomatic complexity rises.

```kotlin
class CheckoutViewModel @Inject constructor(
    private val validateCart: ValidateCartUseCase,
    private val placeOrder: PlaceOrderUseCase
) : ViewModel() {
    fun checkout() = viewModelScope.launch {
        val valid = validateCart()
        if (valid) placeOrder()
    }
}
```

Read more: <https://developer.android.com/topic/architecture/domain-layer>

**Q19.** How do you approach error handling architecture—do you use `Result` types, exceptions, or domain-specific error models?

Use a sealed class `Result<<out T>` with `Success`, `Error`, and `Loading` states at the repository and UseCase boundaries. Inside the domain layer, use domain-specific error sealed classes (e.g., `NetworkError`, `AuthError`, `ValidationError`) rather than raw exceptions to enable exhaustive handling. Catch platform exceptions (IOException, HttpException) at the data layer and map them to domain errors. Avoid using exceptions for control flow in business logic. In ViewModels, reduce errors to UI state fields (`errorMessage`, `isRetryable`). For one-shot operations, expose `Result` via `SharedFlow` events. For streams, embed error state in the UI state object. Never propagate unhandled exceptions to the UI layer. Log unexpected errors centrally via a crash reporter interface. This approach makes error paths explicit and testable.

```kotlin
sealed class DomainError {
    object Network : DomainError()
    data class Validation(val field: String) : DomainError()
}
suspend fun <T> safeCall(block: suspend () -> T): Result<T, DomainError> = try {
    Result.Success(block())
} catch (e: IOException) { Result.Error(DomainError.Network) }
```

Read more: <https://developer.android.com/topic/architecture/ui-layer>

**Q20.** Describe how you would structure a feature module to be reusable across multiple apps in a product suite.

Build the feature as a self-contained dynamic or library module with no app-level dependencies. Define its public API as a minimal interface module (API module) containing UseCases, models, and entry points; hide implementation details in an internal module. Use dependency injection with a feature-specific component that the host app initializes. Avoid hardcoded resources—accept them via constructor or configuration objects. Provide default themes that can be overridden. Expose navigation via deep link contracts or a feature launcher interface, not direct Fragment classes. Keep networking and database schemas internal; expose only domain models. Version the module independently using semantic versioning. Document integration steps and required permissions. Test the feature in isolation using a demo app module before integrating into production apps.

```kotlin
// Public API
interface FeatureLauncher { fun launch(context: Context, config: FeatureConfig) }
// Internal implementation
class FeatureLauncherImpl @Inject constructor(...) : FeatureLauncher { ... }
```

Read more: <https://developer.android.com/topic/modularization>

***

### Jetpack Compose & UI

**Q21.** How does Compose's recomposition model differ from the traditional View system's invalidate/measure/layout cycle?

Traditional Views use an imperative object tree where mutations trigger `invalidate()`, leading to a measure/layout/draw pass on the UI thread managed by `ViewRootImpl`. Compose uses a declarative function-based model where the framework automatically recomposes only functions reading changed state, skipping unaffected subtrees. Recomposition is optimistic and concurrent—Compose can execute composable functions in parallel on multiple cores, then apply changes atomically. There is no separate measure/layout phase for simple cases; layout is part of composition. State reads are tracked at the composition level via snapshots, not via listener patterns. Compose eliminates `findViewById` and manual view updates but requires thinking in state and side-effect boundaries. The system is more efficient for dynamic UIs but has a learning curve for custom layouts.

```kotlin
@Composable
fun Counter() {
    var count by remember { mutableIntStateOf(0) }
    Button(onClick = { count++ }) { Text("Count: $count") }
    // Only Button and Text recompose when count changes; siblings skip
}
```

Read more: <https://developer.android.com/jetpack/compose/mental-model>

**Q22.** What techniques do you use to optimize recomposition in deeply nested Composable hierarchies?

Use `remember` to cache expensive calculations across recompositions. Hoist state to the lowest common ancestor to minimize the recomposition scope. Use `derivedStateOf` to prevent recompositions when derived values haven't actually changed. Pass stable types (immutable data classes, primitives) as parameters; unstable types cause unnecessary recompositions. Apply `@Stable` or `@Immutable` annotations to custom classes if they meet contracts. Use `key` to help Compose identify list items and skip unchanged rows. Avoid passing lambdas that capture unstable references; use `remember` with keys to stabilize callbacks. Use `LaunchedEffect` and `DisposableEffect` with precise keys to control side-effect granularity. Profile with Layout Inspector's recomposition counts. For large lists, use `LazyColumn` with content types and keys. Keep composables skippable by avoiding non-local state reads.

```kotlin
@Composable
fun ItemList(items: List<Item>) {
    LazyColumn {
        items(items, key = { it.id }) { item ->
            ListItem(item) // stable key enables skipping
        }
    }
}
```

Read more: <https://developer.android.com/jetpack/compose/lifecycle>

**Q23.** How do you handle state hoisting when a Composable is used across multiple screens with different state requirements?

Design the Composable to accept state and event lambdas as parameters (stateless pattern), pushing state ownership to callers. Define a reusable stateless Composable like `MyComponent(state: MyState, onEvent: (Event) -> Unit)`. For each screen, create a wrapper or use the Composable directly with screen-specific ViewModel state. If the component needs internal transient UI state (e.g., animation progress), keep it internal with `remember`, but expose all user-meaningful state. Use composition locals sparingly—they obscure the state source. For shared behavior, create a `rememberMyComponentState()` helper that returns a state holder, allowing callers to optionally hoist or use defaults. Document which state is required vs optional. This maximizes reusability while preserving testability and predictable behavior across screens.

```kotlin
@Composable
fun SearchBar(query: String, onQueryChange: (String) -> Unit, onSearch: () -> Unit) {
    TextField(value = query, onValueChange = onQueryChange)
    Button(onClick = onSearch) { Text("Search") }
}
```

Read more: <https://developer.android.com/jetpack/compose/state-hoisting>

**Q24.** Explain the difference between `remember`, `rememberSaveable`, and `derivedStateOf` with concrete use cases.

`remember` caches a value in Composition for the lifetime of the composable; use it for UI calculations, objects, or mutable state (`remember { mutableStateOf(0) }`) that should survive recompositions but not configuration changes. `rememberSaveable` persists state across process death and configuration changes using the saved instance state mechanism; use it for user input, scroll position, or navigation state that must survive rotation. `derivedStateOf` creates a state object computed from other states, but only triggers recomposition when the derived result actually changes; use it for expensive filtering or boolean flags derived from lists (e.g., `derivedStateOf { items.isNotEmpty() }`). Without `derivedStateOf`, every list change recomposes the consumer even if `isNotEmpty` hasn't flipped. Choose `remember` for ephemeral UI, `rememberSaveable` for process-critical UI, and `derivedStateOf` for computed observables.

```kotlin
var text by remember { mutableStateOf("") }
var index by rememberSaveable { mutableIntStateOf(0) }
val isValid by remember { derivedStateOf { text.length > 3 } }
```

Read more: <https://developer.android.com/jetpack/compose/state>

**Q25.** How do you integrate Compose into an existing large codebase using the legacy View system?&#x20;

Adopt Compose incrementally by wrapping Composables in `ComposeView` within existing Fragments or Activities. Start with new screens or isolated UI components (lists, cards) rather than rewriting entire flows. Use `AndroidView` to embed legacy Views inside Compose when needed for gradual migration. Maintain shared ViewModels to bridge state between legacy and Compose screens. Use a common theme adapter to map existing View-based theme attributes to Compose `MaterialTheme`. Set `isolatedFragments` or use single-fragment containers to contain Compose adoption. Ensure your navigation solution supports both fragment destinations and composable destinations. Keep business logic in framework-agnostic layers so it doesn't need rewriting. Train the team with focused workshops on state hoisting and side effects to prevent anti-patterns.

```kotlin
class LegacyFragment : Fragment() {
    override fun onCreateView(...) = ComposeView(requireContext()).apply {
        setContent { MaterialTheme { MyComposeScreen() } }
    }
}
```

Read more: <https://developer.android.com/jetpack/compose/interop>

**Q26.** What is your strategy for theming and design system adoption in Compose at scale?

Build a centralized design system module exposing `MaterialTheme` with custom color, typography, and shape schemes. Define tokens (design primitives) as immutable Kotlin objects generated from Figma or design specs. Avoid hardcoding colors or dimensions in feature modules—require all UI to consume theme values. Use composition locals for semantic colors (e.g., `primaryContainer`, `onSurfaceVariant`) rather than literal hex values. Create reusable component composables (buttons, chips) in the design system module that enforce accessibility and interaction specs. Use `PreviewParameterProvider` to preview components in all theme variants (light, dark, dynamic). Automate design token generation with a code generator to prevent drift. Enforce via lint or code review that feature modules don't import `androidx.compose.ui.graphics.Color` directly for literals. Document the system with a catalog app.

```kotlin
@Composable
fun MyAppTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
    val colors = if (darkTheme) DarkColorScheme else LightColorScheme
    MaterialTheme(colorScheme = colors, typography = AppTypography, content = content)
}
```

Read more: <https://developer.android.com/jetpack/compose/themes>

**Q27.** How do you test Composables that rely on `LaunchedEffect` or `DisposableEffect`?

Use `createComposeRule()` from `androidx.compose.ui:ui-test-junit4` to set the Composition's `CoroutineContext` to a `TestDispatcher` you control. For `LaunchedEffect`, advance time or run pending coroutines via `testDispatcher.scheduler.runCurrent()` or `advanceUntilIdle()`. Mock or stub dependencies injected into the composable so effects don't hit real APIs. For `DisposableEffect`, verify cleanup by disposing the composition via `composeTestRule.disposeComposition()` or navigating away. Use `CompositionLocalProvider` to override dependencies in tests. Test the side effect's outcome (state change, callback invocation) rather than the effect itself. For time-based effects, use `kotlinx-coroutines-test` to advance virtual time. Keep effects small and delegate logic to testable suspend functions or classes. Avoid testing the framework; test your code inside the effect.

```kotlin
@get:Rule val composeTestRule = createComposeRule()
@Test fun testEffect() {
    composeTestRule.setContent { MyScreen() }
    composeTestRule.waitForIdle()
    composeTestRule.onNodeWithText("Loaded").assertExists()
}
```

Read more: <https://developer.android.com/jetpack/compose/testing>

**Q28.** What are the pitfalls of using `Modifier` chains extensively, and how do you avoid them?&#x20;

Excessive `Modifier` chaining creates deeply nested lambda allocations and can hurt readability. Reusing modifiers via `remember` or extracting them to variables reduces allocation overhead. Order matters significantly—`padding` before `background` yields different visual results than after, causing subtle UI bugs. Avoid conditional modifier application that changes the chain structure; use `then()` or conditional values within a single chain. Don't pass large modifier objects through many layers—apply them at the leaf composables. Using `Modifier` as a parameter for every custom composable is good practice, but document whether the modifier replaces or appends to internal modifiers. Performance-wise, prefer `Modifier` over custom `Layout` when possible, but profile if chains exceed \~10 modifiers. Extract reusable patterns into extension functions for consistency.

```kotlin
val cardModifier = Modifier
    .fillMaxWidth()
    .padding(16.dp)
    .background(MaterialTheme.colorScheme.surface)
    .clip(RoundedCornerShape(8.dp))
Card(modifier = cardModifier.then(clickModifier)) { ... }
```

Read more: <https://developer.android.com/jetpack/compose/modifiers>

**Q29.** How do you handle animations in Compose while maintaining 60fps on low-end devices?

Use `animate*AsState` for simple value animations and `AnimatedVisibility`/`AnimatedContent` for container transitions—these are optimized by the framework. Avoid animating large lists or complex layouts simultaneously. Use `LazyColumn` item animations sparingly. Prefer `Modifier.graphicsLayer` for transformations (scale, alpha, rotation) since it renders off the main thread on supported devices. Minimize allocations inside animation frames by hoisting animatables and remembering update lambdas. Use `snapTo` for instant state changes that don't need interpolation. Profile with Android Studio's GPU profiler and `Macrobenchmark` to identify dropped frames. Reduce animation complexity based on device capabilities using `WindowInsets` or system settings (reduced motion). Test on physical low-end devices, not just emulators. Use `SideEffect` or `DerivedState` to precompute animation targets.

```kotlin
val alpha by animateFloatAsState(targetValue = if (visible) 1f else 0f, label = "alpha")
Box(modifier = Modifier.graphicsLayer(alpha = alpha)) { ... }
```

Read more: <https://developer.android.com/jetpack/compose/animation>

**Q30.** Describe your approach to building accessible Composables—what tools and testing practices do you enforce?

Use semantic properties like `contentDescription`, `heading()`, and `stateDescription` to communicate meaning to TalkBack. Ensure touch targets are at least 48dp using `minimumInteractiveComponentSize`. Use `Modifier.semantics` to merge or clear child semantics when a group acts as a single component. Test with TalkBack enabled on physical devices, navigating via swipe gestures. Use the Accessibility Scanner tool to catch missing labels or small touch targets. Write Compose UI tests that query semantics nodes (`onNodeWithContentDescription`, `onNodeWithText`) rather than implementation details. Support keyboard navigation with `focusable()` and `onKeyEvent`. Respect `AccessibilityManager.isEnabled` to adapt behavior if needed, but don't remove functionality. Test color contrast ratios against WCAG guidelines. Document accessibility requirements in component specs and block PRs that fail scanner checks.

```kotlin
IconButton(
    onClick = { },
    modifier = Modifier.semantics { contentDescription = "Add to favorites" }
) { Icon(Icons.Default.Favorite, contentDescription = null) }
```

Read more: <https://developer.android.com/jetpack/compose/accessibility>

***

### Performance & Optimization

**Q31.** How do you diagnose and fix ANRs in production, not just during development?

Integrate Firebase Performance Monitoring or custom ANR watchdog libraries to capture stack traces and main thread states from production. Analyze the `traces.txt` or `data/anr` logs via Play Console or Crashlytics to identify the blocked thread. Look for long-running operations on the main thread: database queries on UI thread, heavy bitmap decoding, JSON parsing, or lock contention. Use `StrictMode` in debug builds to catch disk/network access early. Fix by moving work to background threads with coroutines (`Dispatchers.IO`), using `AsyncLayoutInflater` for heavy layouts, or breaking transactions into smaller chunks. If the ANR is in system code (Binder timeout), reduce IPC calls or batch them. Profile with Systrace to visualize thread blocking. Communicate fixes through staged rollouts to verify resolution.

```kotlin
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder()
    .detectDiskReads().detectDiskWrites().detectNetwork()
    .penaltyLog().build())
```

Read more: <https://developer.android.com/topic/performance/vitals/anr>

**Q32.** What is your process for investigating memory leaks using the Memory Profiler and Heap Dumps?

Capture a heap dump in Android Studio after reproducing the suspected leak path. Convert the HPROF file using `hprof-conv` if needed, then analyze with the Memory Profiler or Eclipse MAT. Look for retained sizes of Activity or Fragment classes; if instances survive after `onDestroy`, there's a leak. Use dominator trees to find what keeps them alive—common culprits are listeners, anonymous inner classes, or static fields holding Views. Check for `ViewModel` leaks via retained `Job` references. Use LeakCanary in debug builds for automatic leak detection with reference chain analysis. In production, use `androidx.metrics` or Firebase Performance to track heap growth trends. Fix by removing static references, using `WeakReference`, cancelling coroutines in lifecycle teardown, or unregistering listeners. Re-dump after fixes to confirm retained count drops to zero.

```kotlin
// LeakCanary integration
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        if (BuildConfig.DEBUG) LeakCanary.install(this)
    }
}
```

Read more: <https://developer.android.com/studio/profile/memory-profiler>

**Q33.** How do you optimize RecyclerView scrolling performance when dealing with heterogeneous view types and images?

Use `RecycledViewPool` to share view holders across multiple `RecyclerViews` if applicable. Ensure `onCreateViewHolder` and `onBindViewHolder` do minimal work—no data formatting or image decoding inside bind. Use Glide or Coil with appropriate `resize()`/`override()` targets to load scaled bitmaps matching view dimensions. Enable `setHasFixedSize(true)` when the adapter content doesn't change RecyclerView's own size. For heterogeneous types, ensure view type integers are stable and don't create excessive holder variations. Use `setItemViewCacheSize` for preloading offscreen items. Move heavy image loading to background threads via the image library's built-in threading. Avoid nested `RecyclerViews` or use `ConcatAdapter` instead. Profile with GPU rendering profile bars to identify jank. Use `AsyncListDiffer` for efficient diffing and animations without full dataset refreshes.

```kotlin
override fun onBindViewHolder(holder: VH, position: Int) {
    val item = getItem(position)
    Glide.with(holder.image).load(item.url).override(400, 400).into(holder.image)
}
```

Read more: <https://developer.android.com/topic/performance/rendering>

**Q34.** What strategies do you use to reduce APK size in a multi-module app with heavy native dependencies?

Enable R8 full mode and ProGuard with aggressive shrinking and obfuscation. Strip debug symbols from native libraries using `android:extractNativeLibs="true"` and `android:useLegacyPackaging="false"` in Gradle 3.6+. Use Android App Bundles (AAB) to deliver only required ABI splits to devices. Move heavy assets to dynamic feature modules or download at runtime via Play Asset Delivery. Compress images to WebP (lossy or lossless) and use vector drawables for icons. Audit dependencies with `./gradlew app:dependencies` to remove unused libraries or replace heavy ones with lighter alternatives. Use `resConfigs` to strip unused language resources. Enable code shrinking per module. For native code, build separate `.so` files per ABI rather than universal binaries. Use `android:allowBackup="false"` cautiously, but primarily focus on asset optimization and dynamic delivery.

```groovy
android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}
```

Read more: <https://developer.android.com/topic/performance/reduce-apk-size>

**Q35.** How do you benchmark app startup time, and what specific optimizations have you implemented?

Use the Jetpack Macrobenchmark library with `StartupTimingMetric` to measure cold, warm, and hot startup times consistently. Identify bottlenecks via method tracing and Systrace during `Application.onCreate` and first Activity launch. Optimizations include: lazy-initializing DI graph using `lazy` or `provider` patterns rather than eager singletons. Moving heavy WorkManager initialization to background. Using `ContentProvider` initialization sparingly—libraries like Firebase often add hidden providers; remove or defer them. Implementing splash screens via `SplashScreen` API to mask loading. Preloading critical classes with `AppComponentFactory` or `Baseline Profiles`. Reducing `onCreate` layout complexity by inflating only the initial visible portion. A/B testing startup improvements via staged rollouts. Measuring real-user startup times via Firebase Performance Monitoring.

```kotlin
class App : Application() {
    val appComponent by lazy { DaggerAppComponent.factory().create(this) }
    override fun onCreate() {
        super.onCreate()
        // defer heavy init
    }
}
```

Read more: <https://developer.android.com/topic/performance/benchmarking/macrobenchmark-overview>

**Q36.** Explain how you would optimize battery consumption for an app using location services in the background.

Use the Fused Location Provider with balanced power accuracy (`PRIORITY_BALANCED_POWER_ACCURACY`) rather than high accuracy unless necessary. Batch location updates by setting the smallest displacement and interval margins appropriately. Use the `Passive` provider when the app doesn't need to actively request but can receive updates triggered by other apps. Implement geofencing via `GeofencingClient` instead of polling location for region detection. Move location work to a foreground service only when the user explicitly expects tracking; otherwise use `WorkManager` with constraints. Avoid wake locks; let the system schedule work in Doze-friendly windows. Use `BroadcastReceiver` for significant motion detection to start location updates only when the user is moving. Cache and deduplicate location data before network transmission. Test battery drain with Android Studio's Energy Profiler.

```kotlin
val request = LocationRequest.Builder(Priority.PRIORITY_BALANCED_POWER_ACCURACY, 60_000L)
    .setMinUpdateDistanceMeters(100f).build()
fusedLocationClient.requestLocationUpdates(request, callback, Looper.getMainLooper())
```

Read more: <https://developer.android.com/training/location>

**Q37.** What are the trade-offs between using ProGuard, R8, and DexGuard in a commercial app?

ProGuard is the legacy shrinker/obfuscator with broad community rules but slower build times and no D8 integration; Google deprecated it in favor of R8. R8 is the default Android shrinker, combining shrinking, desugaring, obfuscating, and dexing in one step for faster builds and smaller DEX output. It supports ProGuard rules but has slightly different optimization semantics that can cause subtle runtime bugs if rules are incomplete. DexGuard is a commercial extension of ProGuard with stronger string encryption, reflection replacement, asset encryption, and runtime application self-protection (RASP) for anti-tampering. Trade-offs: R8 is free, fast, and officially supported but offers basic obfuscation. DexGuard adds security layers valuable for finance/DRM apps but increases build complexity and cost. For most apps, R8 with proper rules is sufficient. Choose DexGuard only if reverse engineering is a critical business threat and you need runtime protection beyond obfuscation.

```groovy
android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles 'proguard-rules.pro'
        }
    }
}
```

Read more: <https://developer.android.com/studio/build/shrink-code>

**Q38.** How do you handle large bitmaps without running into `OutOfMemoryError`?

Load bitmaps via `BitmapFactory.Options` with `inJustDecodeBounds=true` to read dimensions first, then calculate `inSampleSize` to downscale to view dimensions. Use Glide or Coil, which handle subsampling and caching automatically. Store Bitmaps in `BitmapPool` (Glide) for reuse rather than allocating new ones per image. Use `inBitmap` on Android 3.0+ to reuse memory from recycled bitmaps. Prefer `RGB_565` over `ARGB_8888` for opaque images to halve memory usage. Never hold bitmap references in static fields or long-lived objects. Recycle bitmaps manually only when not using a managed library. For very large images (maps, documents), use tile-based rendering with `BitmapRegionDecoder` or libraries like Subsampling Scale Image View. Monitor heap size with `Runtime.getRuntime().maxMemory()` before loading unknown-size images.

```kotlin
val options = BitmapFactory.Options().apply {
    inJustDecodeBounds = true
    BitmapFactory.decodeResource(resources, R.id.myimage, options)
    inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
    inJustDecodeBounds = false
}
val bitmap = BitmapFactory.decodeResource(resources, R.id.myimage, options)
```

Read more: <https://developer.android.com/topic/performance/graphics>

**Q39.** What tools do you use to profile jank and frame drops, and what are the most common causes you've fixed?

Use Systrace/Android Studio CPU Profiler to capture traces and identify frames exceeding 16ms (60fps). Enable GPU rendering profile bars on device to visualize frame times in real time. Use Jetpack Macrobenchmark with `FrameTimingMetric` for automated regression detection. Common causes: overdraw from redundant backgrounds (fix by removing unnecessary `android:background` and using `clipToPadding="false"` wisely), nested `LinearLayouts` causing excessive measure passes (replace with `ConstraintLayout`), heavy work on main thread during scroll (move to background), bitmap decoding without resizing, frequent garbage collection from object churn (use object pools), and unnecessary `requestLayout` calls triggering full view hierarchy remeasure. Complex shadows and elevation also increase render thread workload. Fixing usually involves flattening layouts, caching calculations, and offloading I/O.

```kotlin
// Macrobenchmark
class FrameBenchmark {
    @get:Rule val benchmarkRule = MacrobenchmarkRule()
    @Test fun scroll() = benchmarkRule.measureRepeated(packageName, listOf(FrameTimingMetric())) {
        pressHome(); startActivityAndWait()
        val recycler = device.findObject(By.res(packageName, "recycler"))
        recycler.setGestureMargin(device.displayWidth / 5)
        recycler.fling(Direction.DOWN)
    }
}
```

Read more: <https://developer.android.com/topic/performance/rendering>

**Q40.** How do you optimize Room database queries for screens that display complex relational data?&#x20;

Use `@Relation` with `@Embedded` and `@Transaction` for one-to-many fetches, but be aware they run as separate queries wrapped in a transaction. For complex joins, write custom `@Query` with SQLite JOINs to fetch flattened data in a single query rather than multiple entity lookups. Use database views (`@DatabaseView`) to precompute complex relational results. Add indices on foreign keys and frequently filtered columns to avoid full table scans. Use `LIMIT` and `OFFSET` (or Room's Paging 3 integration) for large datasets rather than loading all rows. Select only needed columns instead of `SELECT *`. Use `Flow` queries for reactive updates but ensure they emit only when relevant tables change. Profile slow queries with `EXPLAIN QUERY PLAN` in SQLite. Avoid nesting transactions. Use `RoomDatabase.Builder.setQueryCallback` in debug to log and analyze query execution times.

```kotlin
@Query("SELECT u.*, COUNT(o.id) as orderCount FROM users u LEFT JOIN orders o ON u.id = o.userId GROUP BY u.id")
fun getUsersWithOrderCount(): Flow<List<UserWithCount>>
```

Read more: <https://developer.android.com/training/data-storage/room>

***

### Networking, Data & Storage

**Q41.** How do you design a network layer that gracefully handles flaky connectivity and retries?

Use OkHttp with an `Interceptor` that detects network errors and applies exponential backoff retries via `RetryInterceptor`. Implement a `NetworkMonitor` using `ConnectivityManager` to queue requests offline and flush when online. Use Retrofit with coroutines and wrap calls in `Result` types. Apply `Resilience4j` or a custom circuit breaker pattern to stop hammering failing endpoints. Use `WorkManager` for guaranteed eventual delivery of mutations. Set reasonable timeouts (`connectTimeout`, `readTimeout`, `writeTimeout`) rather than defaults. Cache GET responses with `Cache-Control` headers and OkHttp cache for stale-while-revalidate behavior. Provide user feedback via UI state (skeletons, offline banners) rather than silent failures. Log network quality metrics to analytics. Test with Charles Proxy throttling and airplane mode toggling.

```kotlin
val client = OkHttpClient.Builder()
    .addInterceptor(HttpLoggingInterceptor())
    .addInterceptor(RetryInterceptor(maxRetry = 3, backoff = ExponentialBackoff()))
    .cache(Cache(cacheDir, 10 * 1024 * 1024))
    .build()
```

Read more: <https://developer.android.com/training/basics/network-ops>

**Q42.** What is your strategy for caching API responses—do you prefer OkHttp cache, Room, or a custom solution?

Use OkHttp cache for simple HTTP-level caching of GET requests with standard cache headers; it's fast and requires no code changes beyond interceptor setup. Use Room for structured caching where you need relational queries, offline-first architecture, or cache invalidation logic tied to business rules rather than HTTP semantics. Use a custom in-memory LRU cache (via `LruCache`) for ephemeral session data or computed results. In practice, combine all three: OkHttp for network cache, Room for offline source-of-truth, and LRU for UI-level memoization. Define cache policies per endpoint—immutable reference data gets long OkHttp cache, user-specific data gets Room with timestamp-based invalidation. Avoid caching sensitive data in OkHttp cache unless encrypted. Use `Cache-Control: max-age` and `ETag` for efficient revalidation. Document cache boundaries to prevent stale data bugs.

```kotlin
@Query("SELECT * FROM articles WHERE lastUpdated > :expiry")
fun getValidArticles(expiry: Long = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1)): Flow<List<<Article>>
```

Read more: <https://developer.android.com/topic/performance/network>

**Q43.** How do you handle API versioning and backward compatibility when the backend ships breaking changes? Use API versioning in URL paths (`/v1/`, `/v2/`) or headers (`Accept: application/vnd.v2+json`). Maintain separate Retrofit service interfaces per version. Map responses to domain models in the data layer so version changes don't leak into UI. Use `Moshi` polymorphic adapters or `JsonQualifier` to handle field renaming or type changes gracefully. Ship app updates with migration logic that handles both old and new payloads during transition windows. Use feature flags to toggle between API versions remotely without app releases. Implement fallback defaults for missing fields to prevent `NullPointerException`. Communicate deprecation schedules with backend teams. Use integration tests against mock servers with both schema versions. Never parse raw JSON in ViewModels—always isolate version handling in repository mappers.

```kotlin
@JsonClass(generateAdapter = true)
data class UserDto(
    val id: String,
    @Json(name = "display_name") val name: String // handles field rename
)
```

Read more: <https://developer.android.com/training/basics/network-ops/connecting>

**Q44.** Explain your approach to syncing local database state with remote server state in a conflict-prone environment. Use a sync queue table in Room tracking local changes with status (`PENDING`, `SYNCED`, `CONFLICT`). Apply optimistic UI updates by writing locally first, then enqueue a WorkManager task to push to server. On pull, fetch server state, compare version vectors or `modified_at` timestamps, and apply a conflict resolution strategy (last-write-wins, server-wins, or custom merge). Flag conflicts in the UI for user resolution when automatic merge fails. Use UUIDs for primary keys to prevent collision during offline creation. Batch sync operations to reduce API calls. Implement exponential backoff with jitter for retry. Maintain a `sync_token` or `ETag` for incremental sync. Test conflict scenarios with multiple devices offline then online. Log sync metrics to detect drift. Ensure the server API supports idempotent updates for retried requests.

```kotlin
@Entity
data class SyncedItem(
    @PrimaryKey val id: String = UUID.randomUUID().toString(),
    val content: String,
    val syncStatus: SyncStatus = SyncStatus.PENDING,
    val modifiedAt: Long = System.currentTimeMillis()
)
```

Read more: <https://developer.android.com/topic/architecture/data-layer>

**Q45.** How do you manage API keys and sensitive configuration in a way that resists reverse engineering? Never hardcode API keys in `BuildConfig` or XML resources; they appear as plain strings in APK/AAB. Store keys in native code (JNI/C++) compiled to `.so` files, which raises the reverse engineering barrier though doesn't prevent it. Use remote configuration (Firebase Remote Config or backend endpoint) to deliver keys at runtime, reducing static exposure. Implement certificate pinning to prevent MITM key interception. Rotate keys frequently and support revocation via remote kill switches. Use OAuth 2.0 with short-lived access tokens and refresh tokens stored in EncryptedSharedPreferences or Keystore. For high-security apps, use white-box cryptography or hardware security modules. Apply R8/DexGuard obfuscation to string literals. Monitor API key usage server-side for anomalous patterns. Defense is layered—no single method is sufficient.

```cpp
// native-lib.cpp
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_app_Secrets_getApiKey(JNIEnv* env, jobject) {
    return env->NewStringUTF("secret_key_here");
}
```

Read more: <https://developer.android.com/training/articles/security-key-attestation>

**Q46.** What is your experience with GraphQL on Android, and how does it compare to REST in production? GraphQL reduces over-fetching and under-fetching by letting the client specify exact fields, which is efficient for mobile bandwidth. Apollo Kotlin generates type-safe models and coroutine-based APIs from schemas, ensuring compile-time contract validation. However, it adds complexity: caching is client-managed (normalized cache) rather than simple HTTP caching, error handling includes partial data scenarios, and file uploads require multipart extensions. In production, REST is simpler for teams, has better tooling, and leverages existing HTTP infrastructure. GraphQL shines when backend is microservices-based or when mobile needs vastly different data shapes than web. Use persisted queries to prevent arbitrary query attacks and improve performance. Monitor query complexity server-side. For Android, Apollo's normalized cache and watchers provide reactive UI updates comparable to Room + Flow but with GraphQL semantics.

```kotlin
val response = apolloClient.query(GetUserQuery(id = "123")).execute()
when (response.data) {
    is GetUserQuery.Data -> display(response.data.user)
}
```

Read more: <https://www.apollographql.com/docs/kotlin/>

**Q47.** How do you implement pagination at the data layer—Paging 3, custom solutions, or a hybrid? Use Jetpack Paging 3 for standard list pagination with `PagingSource` backed by Room or network. It handles loading states, error boundaries, and `RecyclerView` integration via `PagingDataAdapter`. For complex cases (bi-directional pagination, non-list UIs, or custom caching), extend `PagingSource` or use `RemoteMediator` for network+database combined sources. Custom solutions are acceptable when Paging 3's assumptions (linear list, single source) don't fit—e.g., paginated grids with complex headers or pagination within nested structures. Hybrid approaches use Paging 3 for the main list but custom `Flow`-based state machines for auxiliary data. Always expose `LoadState` to UI for skeletons and retry. Use `cachedIn(viewModelScope)` to survive configuration changes. Avoid `PagingSource` for small static lists where complexity outweighs benefit.

```kotlin
class ItemPagingSource(private val api: Api) : PagingSource<Int, Item>() {
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Item> {
        val page = params.key ?: 1
        val response = api.getItems(page)
        return LoadResult.Page(response.items, prevKey = if (page == 1) null else page - 1, nextKey = if (response.hasMore) page + 1 else null)
    }
}
```

Read more: <https://developer.android.com/topic/libraries/architecture/paging>

**Q48.** Describe how you would migrate a production database schema using Room without data loss. Use Room's `Migration` classes implementing `migrate()` with SQLite `ALTER TABLE` and `INSERT INTO ... SELECT` for structural changes. For destructive migrations that preserve data, create a new table with the desired schema, copy data from the old table via SQL, drop the old table, and rename the new one. Version migrations sequentially—never skip versions in production. Test migrations with Room's `MigrationTestHelper` on an actual database file from the previous app version. Ship migrations in debug builds to team members using old versions before release. For complex transformations, use `RoomDatabase.Callback` or `AutoMigration` (Room 2.4+) with `@DeleteTable`, `@RenameTable`, `@DeleteColumn` annotations to reduce boilerplate. Always back up user data to cloud or export before risky migrations. Provide a fallback strategy if migration fails (clear and re-sync).

```kotlin
val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(db: SupportSQLiteDatabase) {
        db.execSQL("CREATE TABLE users_new (id TEXT PRIMARY KEY, name TEXT)")
        db.execSQL("INSERT INTO users_new (id, name) SELECT id, name FROM users")
        db.execSQL("DROP TABLE users")
        db.execSQL("ALTER TABLE users_new RENAME TO users")
    }
}
```

Read more: <https://developer.android.com/training/data-storage/room/migrating>

**Q49.** How do you handle large JSON payloads efficiently without blocking the UI thread? Parse JSON on `Dispatchers.IO` using coroutines or Retrofit's built-in `suspend` support which already offloads to background threads. Use streaming JSON parsers (Moshi's `JsonReader`, Jackson streaming API) for payloads exceeding memory limits instead of loading entire DOM trees. Use Retrofit with `@Streaming` for raw response bodies processed line-by-line. For Room inserts of large parsed datasets, batch insertions in chunks (e.g., 500 rows per transaction) to avoid long SQLite locks. Expose parsing progress via `Flow` if the UI needs a progress bar. Avoid `toString()` on massive JSON for logging. Use gzip compression at the server level. Consider Protocol Buffers or FlatBuffers for structured data that doesn't need human readability—they parse faster and allocate less. Profile parsing with Android Studio CPU profiler.

```kotlin
@Streaming
@GET("large-dump")
suspend fun downloadLarge(): ResponseBody

withContext(Dispatchers.IO) {
    response.byteStream().bufferedReader().useLines { lines ->
        lines.chunked(500).forEach { batch -> dao.insertBatch(batch) }
    }
}
```

Read more: <https://developer.android.com/training/basics/network-ops/xml>

**Q50.** What is your approach to data serialization—Kotlinx Serialization, Moshi, Gson, or Protobuf? Why? Prefer Kotlinx Serialization for Kotlin-first projects—it integrates with language features (sealed classes, null safety, default arguments) without reflection, supports Multiplatform, and generates code at compile time. Use Moshi for Java interoperability or when working with legacy codebases; its Kotlin codegen is reflection-free and fast. Avoid Gson in new projects—it uses reflection, ignores Kotlin null safety, and doesn't support default values reliably. Use Protobuf (Wire or protobuf-lite) for internal high-performance APIs where payload size and parsing speed matter more than human readability. For REST APIs with complex polymorphism, Moshi's polymorphic adapters are mature. For GraphQL, Apollo generates models automatically. The choice depends on team expertise, backend contract format, and performance requirements. Standardize on one per project to avoid dependency bloat.

```kotlin
@Serializable
data class User(val id: String, val name: String = "Unknown")

val json = Json.encodeToString(User(id = "1"))
val user = Json.decodeFromString<User>(json)
```

Read more: <https://kotlinlang.org/docs/serialization.html>

***

### Testing & Quality Assurance

**Q51.** What is your testing pyramid for an Android app, and what coverage thresholds do you enforce? The pyramid base is unit tests (70% coverage target) for ViewModels, UseCases, repositories, and mappers using JUnit5 and MockK/Mockito. The middle layer is integration tests (20%) for database DAOs, repository boundaries, and DI graph verification with Hilt testing. The top is UI/E2E tests (10%) using Espresso or Compose UI tests for critical user journeys (login, checkout). Enforce 80% line coverage on domain layer classes via JaCoCo or Kover; allow lower coverage on UI layer since visual tests supplement it. Run unit tests on every PR; integration tests on merge; E2E tests nightly. Use fakes over mocks for repository tests to validate real behavior. Exclude generated code, Dagger components, and data classes from coverage metrics. Block PRs that drop coverage below thresholds. Track flaky test rates and disable/repair tests that fail intermittently.

```kotlin
@Test
fun `search returns filtered results`() = runTest {
    val fakeRepo = FakeRepo().apply { addItems(listOf(Item("A"), Item("B"))) }
    val vm = SearchViewModel(fakeRepo)
    vm.onQueryChange("A")
    assertEquals(listOf(Item("A")), vm.state.value.results)
}
```

Read more: <https://developer.android.com/training/testing>

**Q52.** How do you write effective UI tests for flows that depend on biometric authentication or hardware sensors? Mock biometric results using `BiometricPrompt` test APIs or wrapper interfaces that can be faked in test builds. For hardware sensors (GPS, accelerometer), inject sensor managers via abstractions and provide fake implementations that emit controlled values during tests. Use Hilt to swap real dependencies for test doubles in `@HiltAndroidTest` scenarios. Avoid testing the actual biometric hardware—test your app's reaction to success/failure callbacks. For camera flows, use `FakeCameraDevice` or mock the camera repository. Structure tests around states: given biometric success, when user triggers action, then expected state occurs. Use IdlingResources to wait for asynchronous biometric callbacks. Run these tests on emulators with simulated fingerprint rather than physical devices to ensure consistency. Keep sensor-dependent tests in a separate test suite that runs on CI with emulator configurations.

```kotlin
@HiltAndroidTest
class PaymentFlowTest {
    @get:Rule val hiltRule = HiltAndroidRule(this)
    @BindValue @JvmField val bioManager: BioManager = FakeBioManager(success = true)
    @Test fun completePayment() { /* tap pay, assert success */ }
}
```

Read more: <https://developer.android.com/training/testing/integration-testing>

**Q53.** What is your strategy for testing ViewModels that interact with multiple UseCases and Repositories? Inject all dependencies via constructor to enable easy mocking or faking. Use `TestDispatcher` (via `Dispatchers.setMain`) to control coroutine timing. Test state transitions by collecting `StateFlow` values with `Turbine` or `testIn(backgroundScope)`. Verify that each user event triggers the correct UseCase invocation with expected parameters. Mock UseCase outputs to simulate success, error, and loading paths. Don't test the internal logic of UseCases in ViewModel tests—that belongs in UseCase unit tests. Verify navigation events or one-shot effects via `SharedFlow` collection. Use `UnconfinedTestDispatcher` for immediate execution or `StandardTestDispatcher` for fine-grained control. Reset `Dispatchers.Main` in `@After` to prevent test pollution. Keep ViewModel tests focused on orchestration, not business rule validation.

```kotlin
@Test
fun `load data updates state`() = runTest {
    val useCase = mockk<GetDataUseCase>(relaxed = true)
    coEvery { useCase() } returns Data()
    val vm = MyViewModel(useCase)
    vm.load()
    assertEquals(Data(), vm.state.value)
}
```

Read more: <https://developer.android.com/topic/libraries/architecture/coroutines>

**Q54.** How do you handle flaky Espresso tests in CI, and what alternatives have you evaluated? Flakiness usually stems from timing issues, animations, or unhandled system dialogs. Disable animations via `adb shell settings put global window_animation_scale 0` in CI. Use `IdlingResource` to synchronize with background work rather than `Thread.sleep()`. Handle system dialogs (runtime permissions, Google Play popups) with UIAutomator or by mocking permission states. Run tests on consistent emulator snapshots with fixed API levels and hardware profiles. Evaluate alternatives: Compose UI tests are more deterministic due to synchronization with the UI thread. Use Firebase Test Lab or AWS Device Farm for broader device coverage and isolation. For CI stability, shard tests across multiple emulator instances and retry failed shards once before marking failure. Track flakiness metrics per test and quarantine consistently flaky tests for repair. Consider screenshot testing for visual regression as a complement to interaction tests.

```kotlin
@Before
fun disableAnimations() {
    val am = InstrumentationRegistry.getInstrumentation().uiAutomation
    am.executeShellCommand("settings put global transition_animation_scale 0")
}
```

Read more: <https://developer.android.com/training/testing/espresso>

**Q55.** What mocking framework do you prefer—MockK, Mockito-Kotlin, or fakes—and why? Prefer fakes (lightweight in-memory implementations) for repository and data source tests because they validate real integration behavior and don't break when internal implementation details change. Use MockK for Kotlin-specific features like coroutines, extension functions, and sealed classes where fakes are impractical; its DSL is idiomatic Kotlin. Use Mockito-Kotlin only in legacy Java-interoperable codebases where MockK adoption is too disruptive. Fakes shine for Room databases (in-memory `Room.inMemoryDatabaseBuilder`), network (MockWebServer), and shared preferences. Mocks are acceptable for external SDKs or complex objects with many methods where fake implementation is burdensome. The rule: if the dependency has behavior worth verifying (caching, query logic), use a fake. If it's a simple callback boundary, use a mock. MockK's relaxed mocks and `coEvery` make coroutine testing ergonomic.

```kotlin
class FakeUserRepo : UserRepo {
    private val data = mutableListOf<User>()
    override suspend fun getUser(id: String) = data.first { it.id == id }
    fun addUser(user: User) = data.add(user)
}
```

Read more: <https://mockk.io/>

**Q56.** How do you test coroutine-based code that involves `Dispatchers.IO` or `Dispatchers.Main`? Never hardcode dispatchers in production code; inject them via constructor or use `Dispatchers.Default` as a default parameter. In tests, inject `StandardTestDispatcher` or `UnconfinedTestDispatcher` to control execution. Use `Dispatchers.setMain(testDispatcher)` in a JUnit `@Before` rule and `Dispatchers.resetMain()` in `@After`. For `Dispatchers.IO`, replace it with the test dispatcher so I/O-bound code runs immediately and deterministically. Use `advanceUntilIdle()` or `runCurrent()` from `kotlinx-coroutines-test` to pump the scheduler. Test timing-sensitive code by advancing virtual time with `advanceTimeBy()`. Avoid `runBlocking` in tests—it masks real async behavior. Use `runTest` from `kotlinx-coroutines-test` which provides a test scope and handles uncaught exceptions. Verify that coroutines are cancelled properly by checking job state or side effect cleanup.

```kotlin
@Before
fun setup() {
    Dispatchers.setMain(StandardTestDispatcher())
}
@After
fun tearDown() {
    Dispatchers.resetMain()
}
@Test fun test() = runTest { /* uses TestDispatcher automatically */ }
```

Read more: <https://kotlinlang.org/docs/coroutines-test.html>

**Q57.** Describe your approach to screenshot testing and how you prevent visual regressions. Use Paparazzi or Shot to render Composables/Views in a JVM environment without physical devices or emulators, capturing bitmaps for comparison. Store reference screenshots in version control. Run diffs in CI on every PR; fail builds when pixel differences exceed a threshold. Focus screenshot tests on design system components and critical screens rather than every UI state to avoid maintenance burden. Handle dynamic data by mocking ViewModels with fixed states. Account for locale, font scale, and dark mode variations using parameterized tests. Review diffs manually when intentional design changes occur, updating references via a CI command or local task. Prevent false positives by disabling animations and using deterministic layouts. Integrate with Pull Request comments to display before/after images for designer review. Separate screenshot tests from functional tests for faster feedback.

```kotlin
@get:Rule val paparazzi = Paparazzi()
@Test fun launchScreen() {
    paparazzi.snapshot { MyTheme { LaunchScreen(UiState.Loaded(mockData)) } }
}
```

Read more: <https://developer.android.com/studio/test/gradle-managed-devices>

**Q58.** How do you enforce code quality standards across a team of developers with varying skill levels? Automate enforcement via Detekt, KtLint, and Android Lint with custom rules integrated into CI; reject PRs with warnings treated as errors. Provide a comprehensive `CONTRIBUTING.md` and architecture decision records (ADRs) documenting patterns. Use code review checklists focusing on architecture, testing, and performance rather than style (which linters handle). Pair programming and mob reviews for complex features spread knowledge. Maintain a "golden path" sample module demonstrating approved patterns. Run static analysis on every commit with pre-commit hooks. Track technical debt in a backlog with estimated impact. Conduct regular architecture katas or refactoring dojos. Measure code quality metrics (cyclomatic complexity, test coverage, lint violations) in dashboards. Make standards educational rather than punitive—explain why rules exist in lint rule documentation.

```yaml
# detekt.yml
complexity:
  LongParameterList:
    active: true
    functionThreshold: 6
style:
  MagicNumber:
    active: true
    ignoreNumbers: ['-1', '0', '1', '2']
```

Read more: <https://developer.android.com/studio/write/lint>

**Q59.** What is your process for conducting thorough code reviews on architectural changes? Require an ADR (Architecture Decision Record) or design doc before review, explaining context, alternatives, and trade-offs. Review in two passes: first for correctness and architecture alignment, second for nitpicks. Verify that new code follows established module boundaries and doesn't introduce circular dependencies. Check test coverage for new logic and question untested edge cases. Ensure state management follows unidirectional flow and lifecycle rules. Validate error handling paths and loading states. Review for performance: unnecessary allocations, main thread work, memory leaks. Verify that public APIs are minimal and well-documented. Use GitHub/GitLab review threads for substantive discussions, resolving before merge. For large changes, request a walkthrough meeting. Block merges on CI passing and at least two approvals for core architecture files. Follow up post-merge to validate metrics and crash rates.

```markdown
## ADR-012: Adopt MVI for Checkout
- Context: Checkout has 6 LiveData sources causing UI bugs
- Decision: Single StateFlow + Event reducer pattern
- Consequences: +predictability, -boilerplate
```

Read more: <https://developer.android.com/studio/write/lint>

**Q60.** How do you test edge cases in background work like WorkManager tasks or foreground services? Use `WorkManagerTestInitHelper` to initialize a test driver that allows synchronous execution and observation of `WorkInfo` states. Test constraints (network, battery) by configuring the test driver to simulate constraint satisfaction. For `CoroutineWorker`, inject `TestDispatcher` and verify that `doWork()` returns `Result.success()`, `failure()`, or `retry()` under different conditions. Test retry policies by verifying `BackoffPolicy` and run attempts. For foreground services, test the service lifecycle with `ServiceTestRule` or Robolectric service tests. Mock system APIs (alarm manager, job scheduler) to test scheduling logic. Verify that work chaining (`beginWith().then()`) executes in correct order by observing output data propagation. Test idempotency by running the same work twice and verifying no duplicate side effects. Use `TestListenableWorkerBuilder` to instantiate workers directly in JVM tests.

```kotlin
@Test
fun testSyncWorker() {
    val context = ApplicationProvider.getApplicationContext<Context>()
    val worker = TestListenableWorkerBuilder<<SyncWorker>(context).build()
    runTest {
        val result = worker.doWork()
        assertThat(result, `is`(ListenableWorker.Result.success()))
    }
}
```

Read more: <https://developer.android.com/topic/libraries/architecture/workmanager>

***

### Security

**Q61.** How do you securely store OAuth tokens and refresh tokens on Android? Store tokens in `EncryptedSharedPreferences` (AES-256 encryption with keys backed by Android Keystore) for standard apps. For higher security, use the Keystore directly to store keys that encrypt a local database or file containing tokens. Never store tokens in plain `SharedPreferences`, external storage, or `Logcat`. Implement automatic token refresh with exponential backoff before expiry. Clear tokens securely on logout by overwriting memory before deletion and clearing `EncryptedSharedPreferences`. Use AccountManager only if integrating with system accounts; it's deprecated for general use. Bind token access to biometric authentication for high-sensitivity apps using `BiometricPrompt` + `CryptoObject`. Limit token scope to minimum required. Monitor for token leakage via certificate pinning and integrity checks. Rotate refresh tokens on every use if the backend supports it.

```kotlin
val masterKey = MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build()
val prefs = EncryptedSharedPreferences.create(context, "tokens", masterKey, AES256_SIV, AES256_GCM)
prefs.edit().putString("access_token", token).apply()
```

Read more: <https://developer.android.com/training/articles/keystore>

**Q62.** What is your approach to certificate pinning, and how do you handle certificate rotation? Pin the intermediate CA certificate rather than leaf certificates to reduce fragility during server certificate rotation. Use OkHttp's `CertificatePinner` with backup pins (pin multiple certificates or CAs) so the app continues working when the primary certificate changes. Implement a reporting mechanism to detect pin failures in production without crashing the app initially—use a "report-only" mode. Rotate certificates well before expiry and update app pins via remote config or app updates with overlap periods. For emergency rotation, have an out-of-band update channel (Firebase Remote Config) to disable pinning temporarily. Test pinning with tools like `mitmproxy` to ensure it blocks untrusted certificates. Document the certificate rotation schedule with DevOps. Use HPKP-like strategies adapted for mobile: short pin lifetimes and graceful degradation.

```kotlin
val pinner = CertificatePinner.Builder()
    .add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
    .add("api.example.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=") // backup
    .build()
val client = OkHttpClient.Builder().certificatePinner(pinner).build()
```

Read more: <https://developer.android.com/training/articles/security-config>

**Q63.** How do you protect against reverse engineering and tampering in a high-risk app? Apply multiple layers: R8/DexGuard obfuscation and string encryption to deter static analysis. Move critical logic to native code (JNI) compiled with obfuscation tools like O-LLVM. Implement root detection and emulator detection using SafetyNet/Play Integrity API to block compromised devices. Verify app signature and integrity at runtime using `PackageManager` and custom checks. Encrypt sensitive assets and configuration files. Use anti-debugging techniques (detecting `ptrace`, timing checks) in native code. Implement certificate pinning to prevent MITM-based dynamic analysis. Obfuscate network protocols beyond standard HTTPS. Use white-box cryptography for key protection. Monitor for tampering via server-side anomaly detection (unexpected API sequences, impossible travel). No layer is foolproof; defense in depth raises the cost of attack.

```kotlin
fun verifyIntegrity(context: Context): Boolean {
    val sig = context.packageManager.getPackageInfo(context.packageName, GET_SIGNATURES).signatures[0]
    return sig.toCharsString() == EXPECTED_SIGNATURE
}
```

Read more: <https://developer.android.com/google/play/integrity>

**Q64.** Explain how Android Keystore System works and when you would use it over EncryptedSharedPreferences. The Keystore System generates and stores cryptographic keys in hardware-backed secure storage (TEE or StrongBox) if available, isolating keys from the app process. Keys can require user authentication (biometric/PIN) for each use via `setUserAuthenticationRequired(true)`. Use Keystore directly when you need hardware-backed key protection, key attestation, or per-use authentication. Use `EncryptedSharedPreferences` for simpler encrypted storage of small strings (tokens, PII) where the encryption key is Keystore-backed but the data lives in preferences. Keystore is lower-level and requires more boilerplate for encrypt/decrypt operations. For high-value keys (payment keys, identity keys), use Keystore with attestation. For general sensitive app data, `EncryptedSharedPreferences` is sufficient and more ergonomic. StrongBox Keystore (dedicated secure hardware) is preferred on devices that support it (API 28+).

```kotlin
val keyGen = KeyGenerator.getInstance("AES", "AndroidKeyStore")
val spec = KeyGenParameterSpec.Builder("my_key", PURPOSE_ENCRYPT or PURPOSE_DECRYPT)
    .setBlockModes(BLOCK_MODE_GCM)
    .setUserAuthenticationRequired(true)
    .setInvalidatedByBiometricEnrollment(true)
    .build()
keyGen.init(spec); keyGen.generateKey()
```

Read more: <https://developer.android.com/training/articles/keystore>

**Q65.** What measures do you take to prevent intent hijacking and exported component vulnerabilities? Set `android:exported="false"` explicitly on all components that don't need external access. For exported components (deep link activities, services), enforce permission checks with `android:permission` and validate caller identity via `getCallingActivity()` or `checkCallingPermission()`. Use explicit intents internally rather than implicit intents to prevent interception. Validate all incoming intent data (URIs, extras) before processing; reject malformed or unexpected schemes. For `ContentProvider` exports, use path permissions and grant URI permissions temporarily with `FLAG_GRANT_READ_URI_PERMISSION`. Remove debuggable exported components before release. Use `android:protectionLevel="signature"` for custom permissions between your apps. Audit the manifest with `aapt` or static analysis tools for accidental exports. Test with malicious intent fuzzing via Drozer or custom scripts.

```xml
<<activity android:name=".DeepLinkActivity" android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <data android:scheme="https" android:host="example.com"/>
    </intent-filter>
</activity>
<<service android:name=".InternalService" android:exported="false"/>
```

Read more: <https://developer.android.com/topic/security/risks/pending-intent>

**Q66.** How do you handle root detection and emulator detection in financial or healthcare apps? Use Play Integrity API (modern replacement for SafetyNet) to attest device integrity and app legitimacy server-side. Check for root indicators: presence of `su` binary, busybox, Magisk files, or writable `/system` paths. Detect emulators via hardware fingerprinting (CPU info, device ID consistency, sensor availability, QEMU traces). However, root detection is an arms race—combine client-side checks with server-side behavioral analysis. If root is detected, don't immediately crash; degrade functionality (disable sensitive features) or require additional authentication. Obfuscate detection logic to hinder bypassing. Use native code for checks to raise the bar. Validate integrity tokens on your server, not client-side. For healthcare/finance, comply with regulatory requirements (PCI-DSS, HIPAA) which may mandate specific tamper resistance. Document your risk acceptance for rooted devices.

```kotlin
val integrityManager = IntegrityManagerFactory.create(context)
val request = IntegrityTokenRequest.builder()
    .setNonce(Base64.encodeToString(bytes, DEFAULT))
    .build()
integrityManager.requestIntegrityToken(request).addOnSuccessListener { /* send token to server */ }
```

Read more: <https://developer.android.com/google/play/integrity>

**Q67.** What is your strategy for obfuscating sensitive business logic beyond standard R8 rules? Move critical algorithms to native libraries compiled with obfuscation and stripping to hide symbols. Use control flow flattening and string encryption via DexGuard or similar commercial tools. Split logic across multiple classes and modules to increase reconstruction difficulty. Use reflection sparingly as it complicates obfuscation but can be used to dynamically load classes. Implement server-side computation for the most sensitive logic (e.g., pricing algorithms, proprietary calculations) so the client is just a display layer. Use JNI to bridge Java/Kotlin to obfuscated C++ code. Apply name obfuscation to package structures. Remove logging and debug symbols from release builds. Use dynamic code loading (DEX from server) cautiously as it triggers Play Protect warnings. Combine obfuscation with runtime integrity checks that verify method signatures haven't been tampered.

```cpp
// native code obfuscated via O-LLVM
extern "C" JNIEXPORT jstring JNICALL
Java_com_app_Secrets_compute(JNIEnv* env, jobject, jstring input) {
    // sensitive algorithm here
}
```

Read more: <https://developer.android.com/studio/build/shrink-code>

**Q68.** How do you implement secure inter-process communication between your app and a SDK or partner app? Use AIDL with custom permissions (`android:permission` requiring signature-level protection) so only your signed apps can bind the service. Validate the calling package name and signature in `onBind()` before returning the binder. Use `Messenger` instead of raw AIDL for simpler message-based IPC with built-in `Handler` threading. Encrypt payloads passed via IPC with keys exchanged through a secure channel (Keystore-backed). Avoid passing file descriptors or sensitive URIs to untrusted processes. Use `ContentProvider` with path permissions and temporary URI grants for data sharing. For high-security scenarios, use `android:isolatedProcess="true"` for sandboxed service execution. Document the IPC surface area and threat model. Use explicit service intents to prevent binding hijacking. Regularly audit IPC entry points with static analysis.

```kotlin
override fun onBind(intent: Intent): IBinder? {
    val caller = callingPackage
    if (caller !in TRUSTED_PACKAGES) return null
    return binder
}
```

Read more: <https://developer.android.com/guide/components/aidl>

**Q69.** What are the risks of using `WebView` for sensitive flows, and how do you mitigate them? WebView runs in the app's process with extensive attack surface: JavaScript injection, XSS, SSL error handling bypasses, and file access vulnerabilities. Mitigate by disabling JavaScript unless required (`setJavaScriptEnabled(false)`). Use `addJavascriptInterface` only with `@JavascriptInterface` and validate all inputs; better yet, use `WebMessage` for communication. Handle `onReceivedSslError` properly—don't auto-proceed on errors. Restrict content to HTTPS only (`setMixedContentMode(MIXED_CONTENT_NEVER_ALLOW)`). Prevent file access with `setAllowFileAccess(false)`. Use a separate process for WebView (`android:process=":webview_process"`) to isolate crashes and memory leaks. Keep WebView updated via Google Play Services `WebView` implementation. Use `WebViewAssetLoader` for local content instead of `file://` URLs. Monitor for known CVEs in the WebView component. For highly sensitive flows, prefer native implementation over WebView.

```kotlin
webView.settings.apply {
    javaScriptEnabled = false
    allowFileAccess = false
    mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW
}
```

Read more: <https://developer.android.com/develop/ui/views/layout/webapps>

**Q70.** How do you ensure compliance with GDPR or CCPA data handling requirements in your Android app? Implement a consent management platform (CMP) that captures user consent before initializing analytics or advertising SDKs. Provide in-app privacy settings allowing users to view, export, and delete their data. Minimize data collection to what's necessary (data minimization). Use encryption for stored personal data and secure transmission (TLS 1.2+). Anonymize or pseudonymize identifiers where possible. Maintain a data processing inventory documenting what each SDK collects. Support "right to be forgotten" by clearing local data and invoking backend deletion APIs. Display a privacy policy before account creation. Use Firebase Remote Config or backend flags to toggle tracking SDKs off for users who deny consent. Audit third-party libraries for unauthorized data transmission. Document lawful basis for processing. Conduct privacy impact assessments for new features.

```kotlin
fun onConsentDenied() {
    FirebaseAnalytics.getInstance(context).setAnalyticsCollectionEnabled(false)
    prefs.edit().clear().apply() // clear local PII
    backend.deleteUserData(userId)
}
```

Read more: <https://developer.android.com/privacy-and-security>

***

### Background Processing & System Integration

**Q71.** How do you choose between WorkManager, Foreground Service, AlarmManager, and JobScheduler for a given task? Use WorkManager for deferrable, guaranteed background work that needs to survive reboots and app restarts (sync, uploads). Use Foreground Service only for user-initiated long-running tasks that need immediate execution and a persistent notification (music playback, active navigation). Use AlarmManager (or `AlarmManagerPlus` with exact alarm permissions) only for time-critical alarms or reminders where WorkManager's flex periods are insufficient—be aware of Android 12+ exact alarm restrictions. JobScheduler is the system API underlying WorkManager; use it directly only if you need scheduling features WorkManager doesn't expose (API 21+). For immediate execution of important work, use `Expedited Work` in WorkManager (API 31+). Never use `AlarmManager` for polling; use `WorkManager` with constraints. Match the API to user expectations and system resource policies.

```kotlin
val work = OneTimeWorkRequestBuilder<<UploadWorker>()
    .setConstraints(Constraints.Builder().setRequiredNetworkType(CONNECTED).build())
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    .build()
WorkManager.getInstance(context).enqueue(work)
```

Read more: <https://developer.android.com/topic/libraries/architecture/workmanager>

**Q72.** What are the implications of Android 12+ restrictions on foreground services and exact alarms? Android 12 (API 31) requires `FOREGROUND_SERVICE` permission and restricts background app starts; foreground services must show a notification immediately and fall under specific use-case types (location, media, etc.). Apps targeting API 33+ need `USE_EXACT_ALARM` permission or `SCHEDULE_EXACT_ALARM` with user-granted alarm capability; the latter can be revoked by the user or system. These restrictions prevent apps from waking the device excessively and improve battery life. You must declare service types in the manifest (`android:foregroundServiceType`). For exact alarms, prefer inexact alarms or WorkManager unless the use case genuinely requires precision (alarm clock, calendar reminder). Handle `ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED` to react to permission revocation. Test behavior changes on API 31+ emulators and devices. Document service justifications for Play Store review.

```xml
<<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<<service android:name=".MusicService" android:foregroundServiceType="mediaPlayback" />
```

Read more: <https://developer.android.com/about/versions/12/behavior-changes-12>

**Q73.** How do you implement reliable push notification delivery using FCM, especially for time-sensitive data? Use FCM high priority messages (`priority: high`) for time-sensitive data, but sparingly—abuse leads to app standby bucket demotion. Include collapse keys to replace pending notifications with updated content. Implement a local notification queue: if FCM is received while the app is in the background or killed, use a `FirebaseMessagingService` to process data payloads and trigger local notifications immediately. For guaranteed delivery, persist the message in Room upon receipt and show a notification; sync with server when online. Handle token rotation by updating your backend registration on `onNewToken`. Test delivery with Firebase Console and `topic` broadcasts. Monitor delivery receipts and BigQuery exports to diagnose drop-offs. Use notification channels with appropriate importance levels. Respect Do Not Disturb and notification permission states (Android 13+).

```kotlin
class MyFirebaseService : FirebaseMessagingService() {
    override fun onMessageReceived(msg: RemoteMessage) {
        val data = msg.data
        showNotification(data)
        saveToLocalDb(data)
    }
}
```

Read more: <https://firebase.google.com/docs/cloud-messaging>

**Q74.** Describe how you would design a background sync engine that respects Doze mode and App Standby. Use WorkManager with network and battery constraints to schedule syncs during maintenance windows when the device exits Doze. Implement exponential backoff for retries (`BackoffPolicy.EXPONENTIAL`) to avoid waking the device repeatedly. Batch sync operations into a single periodic work request rather than multiple separate jobs. Use `setRequiresBatteryNotLow(true)` and `setRequiresCharging(true)` for heavy syncs to defer until optimal conditions. For high-priority syncs, use expedited work (API 31+) which runs immediately if constraints are met. Listen for connectivity changes via `ConnectivityManager` to trigger opportunistic syncs when the device is active. Maintain a local queue of pending changes; process it during sync windows. Avoid holding wake locks—let the system manage execution. Test with `adb shell dumpsys deviceidle` to force Doze states and verify deferred execution.

```kotlin
val syncWork = PeriodicWorkRequestBuilder<<SyncWorker>(1, TimeUnit.HOURS)
    .setConstraints(Constraints.Builder()
        .setRequiredNetworkType(NetworkType.UNMETERED)
        .setRequiresBatteryNotLow(true)
        .build())
    .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.MINUTES)
    .build()
```

Read more: <https://developer.android.com/training/monitoring-device-state/doze-standby>

**Q75.** How do you handle Bluetooth LE scanning and connections while managing battery impact? Use the `BluetoothLeScanner` with `ScanSettings` set to `SCAN_MODE_LOW_LATENCY` only during active user interaction; switch to `SCAN_MODE_LOW_POWER` for background monitoring. Filter scan results with `ScanFilter` by device name or service UUID to reduce processing. Batch scan results and process them off the main thread. Use `PendingIntent`-based scans (API 26+) for background scanning without keeping a service running. Connect using `autoConnect=false` for faster direct connections when the device is known to be available; use `autoConnect=true` for opportunistic reconnection. Disconnect and close `BluetoothGatt` instances promptly when not needed to free native resources. Avoid continuous scanning—scan in intervals with rest periods. Use `BluetoothAdapter.getLeMaximumAdvertisingDataLength()` to optimize payload sizes. Monitor battery drain via Android Studio profiler during extended BLE sessions.

```kotlin
val settings = ScanSettings.Builder()
    .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
    .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
    .build()
val filter = ScanFilter.Builder().setServiceUuid(ParcelUuid(SERVICE_UUID)).build()
scanner.startScan(listOf(filter), settings, scanCallback)
```

Read more: <https://developer.android.com/guide/topics/connectivity/bluetooth>

**Q76.** What is your experience with Android's Camera2/CameraX APIs, and how do you handle device-specific quirks? CameraX is the modern abstraction over Camera2, providing use-case-based APIs (Preview, ImageAnalysis, ImageCapture) with automatic lifecycle management and device compatibility fixes. Use CameraX for most apps; drop to Camera2 only when you need manual sensor control, RAW capture, or burst modes unsupported by CameraX. Device quirks include: aspect ratio distortions, flash timing issues, and orientation bugs on Samsung and Xiaomi devices. Handle them by testing on a broad device lab (top 20 market devices) and using CameraX's `CameraSelector` with `ExtensionMode` for vendor features. For Camera2, maintain a compatibility matrix mapping device models to workaround parameters (preview sizes, flash modes). Use `CameraCharacteristics` to query hardware capabilities before configuring sessions. Always handle `onError` callbacks and recreate the camera session gracefully. Use `Executor` for callbacks to avoid blocking the camera thread.

```kotlin
val imageAnalysis = ImageAnalysis.Builder()
    .setTargetResolution(Size(1280, 720))
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .build()
cameraProvider.bindToLifecycle(lifecycleOwner, CameraSelector.DEFAULT_BACK_CAMERA, preview, imageAnalysis)
```

Read more: <https://developer.android.com/training/camerax>

**Q77.** How do you implement deep linking that works consistently across notification taps, widgets, and external apps? Define deep links in the Navigation Component or manifest intent filters with `android:scheme` and `android:host` patterns. Use a single `Activity` as the entry point with `NavHost` handling routing, or a trampoline activity that validates and dispatches URIs. For notifications, use `PendingIntent` with `NavDeepLinkBuilder` or explicit task stack builders to preserve back stack. For widgets, use `PendingIntent` with `setAction` matching the deep link URI. For external apps, document the URI contract and validate incoming paths in `onCreate` before navigation. Handle invalid or malformed URIs gracefully with a fallback destination. Use `android:autoVerify="true"` for App Links to ensure domain verification. Test deep links via `adb shell am start -W -a android.intent.action.VIEW -d "yourscheme://host/path"`. Ensure the same URI produces identical behavior regardless of entry point.

```kotlin
val pendingIntent = NavDeepLinkBuilder(context)
    .setGraph(R.navigation.nav_graph)
    .setDestination(R.id.productDetail)
    .setArguments(bundleOf("id" to productId))
    .createPendingIntent()
```

Read more: <https://developer.android.com/training/app-links>

**Q78.** What strategies do you use for handling media playback in the background with proper audio focus? Use `MediaSession` and `MediaController` from Jetpack Media3 for consistent playback architecture across foreground services and UI. Request audio focus via `AudioManager.requestAudioFocus()` or `AudioFocusRequest` (API 26+) with `AUDIOFOCUS_GAIN` and ducking behavior appropriate to your content type. Handle focus loss by pausing playback and releasing resources temporarily. Implement a foreground service with `foregroundServiceType="mediaPlayback"` and a persistent notification showing media controls. Use `MediaBrowserServiceCompat` to enable playback from Auto, Wear, and assistant surfaces. Manage audio focus changes and noisy intent (headphone disconnect) via `BroadcastReceiver` or `AudioManager` callbacks. Use `ExoPlayer` for adaptive streaming and background playback consistency. Test audio focus behavior with other media apps (YouTube, Spotify) to verify ducking and resume. Ensure proper notification media controls using `MediaStyle` notifications.

```kotlin
val audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
    .setOnAudioFocusChangeListener { focusChange ->
        when (focusChange) {
            AUDIOFOCUS_LOSS -> player.pause()
            AUDIOFOCUS_GAIN -> player.play()
        }
    }.build()
audioManager.requestAudioFocus(audioFocusRequest)
```

Read more: <https://developer.android.com/guide/topics/media>

**Q79.** How do you manage permissions across Android versions, especially for location, notifications, and Bluetooth? Use the Activity Result API (`registerForActivityResult`) for modern permission requests rather than `onRequestPermissionsResult`. For location, handle foreground (`ACCESS_FINE_LOCATION`) and background (`ACCESS_BACKGROUND_LOCATION`) permissions separately; request background only after foreground is granted, as per Play Store policy. For notifications, request `POST_NOTIFICATIONS` at runtime on Android 13+ using the same result API. For Bluetooth, on Android 12+ replace `BLUETOOTH` and `BLUETOOTH_ADMIN` with runtime permissions `BLUETOOTH_SCAN`, `BLUETOOTH_CONNECT`, and `BLUETOOTH_ADVERTISE`. Abstract permission logic into a reusable `PermissionManager` or `PermissionHandler` that checks API levels and permission states. Show rationale UI explaining why permission is needed before system dialogs. Gracefully degrade features when permissions are denied with "Don't ask again." Test permission flows on API 21, 33, and latest devices. Use `shouldShowRequestPermissionRationale()` to detect permanent denial.

```kotlin
val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
    if (isGranted) startScan() else showRationale()
}
if (ContextCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_SCAN) != PERMISSION_GRANTED) {
    requestPermissionLauncher.launch(Manifest.permission.BLUETOOTH_SCAN)
}
```

Read more: <https://developer.android.com/training/permissions>

**Q80.** Describe your approach to implementing widgets and ensuring they update reliably without draining battery. Use `Glance` (Jetpack) for modern widget development with Compose-like declarative API, reducing boilerplate and improving reliability over traditional `RemoteViews`. Update widgets via `WorkManager` periodic work or `AlarmManager` with exactness scaled to importance, rather than frequent manual updates. Use `updatePeriodMillis` only for coarse updates (minimum 30 minutes); for real-time data, use `setUpdateContent` or push updates via `AppWidgetManager` when data changes. Implement `onEnabled` and `onDisabled` callbacks to register and unregister update observers. Use `ListView` or `GridView` with `RemoteViewsFactory` for collection widgets, caching data in `onDataSetChanged`. Avoid network calls directly in widget update methods; preload data into a cache or database. Respect battery saver mode by reducing update frequency. Test widget behavior during Doze and App Standby. Provide preview layouts and configure options for user customization.

```kotlin
class MyWidget : GlanceAppWidget() {
    override suspend fun provideGlance(context: Context, id: GlanceId) {
        provideContent { WidgetContent() }
    }
}
```

Read more: <https://developer.android.com/jetpack/glance>

***

### Build System, CI/CD & Tooling

**Q81.** How do you structure a Gradle build to support multiple build variants, flavors, and feature modules efficiently? Use the `com.android.application` plugin in the app module and `com.android.library` in feature modules. Define `productFlavors` for dimensions like `environment` (dev, staging, prod) and `distribution` (play, enterprise). Use `buildTypes` (debug, release) with `initWith` to share configuration. Place shared build logic in `buildSrc` or convention plugins (`com.android.application` convention script) to avoid duplication. Use `api` and `implementation` dependencies carefully—feature modules should expose minimal APIs. Configure `sourceSets` per flavor for environment-specific resources and `BuildConfigField` values. Use `androidComponents` DSL (AGP 7.0+) for variant-aware artifact manipulation. Keep `build.gradle.kts` declarative; move complex logic to custom plugins. Use Gradle's `configuration cache` and `build cache` to speed up variant builds. Document flavor dimensions in the project README.

```kotlin
android {
    flavorDimensions += "env"
    productFlavors {
        create("dev") { dimension = "env"; buildConfigField("String", "API_URL", "\"https://dev.api.com\"") }
        create("prod") { dimension = "env"; buildConfigField("String", "API_URL", "\"https://api.com\"") }
    }
}
```

Read more: <https://developer.android.com/studio/build>

**Q82.** What is your strategy for managing dependency versions across a large multi-module project? Use a centralized version catalog (`gradle/libs.versions.toml`) with TOML format to declare versions, libraries, and plugins. Reference them consistently across modules via `implementation(libs.retrofit)` syntax. For shared platform dependencies, use a `platform()` or `enforcedPlatform()` BOM (Bill of Materials) like `compose-bom` to align transitive versions. Define a `buildSrc` or convention plugin that applies common dependencies to all modules automatically. Pin critical dependency versions to avoid transitive upgrades breaking builds. Use Dependabot, Renovate, or custom scripts to automate version update PRs. Review changelogs for breaking changes before merging updates. Separate production and test dependency versions. Avoid `+` dynamic versions in production builds. Document the update cadence (e.g., monthly dependency refresh) and rollback procedures.

```toml
[versions]
kotlin = "1.9.0"
compose = "2024.02.00"

[libraries]
retrofit = { module = "com.squareup.retrofit2:retrofit", version = "2.9.0" }
compose-bom = { module = "androidx.compose:compose-bom", version.ref = "compose" }
```

Read more: <https://developer.android.com/build/migrate-to-catalogs>

**Q83.** How do you optimize Gradle build times for a team of 10+ developers? Enable Gradle build cache (`org.gradle.caching=true`) and configure a remote build cache node (Gradle Enterprise or self-hosted) so CI and local builds share outputs. Enable configuration cache (`org.gradle.configuration-cache=true`) to skip configuration phase on subsequent builds. Increase heap size (`org.gradle.jvmargs=-Xmx8g`). Use modularization to enable parallel compilation and selective module builds (`--parallel`). Avoid heavy computation in `build.gradle.kts` during configuration; use lazy task APIs. Replace kapt with KSP for annotation processing (Room, Moshi, Hilt) for faster processing. Use R8 in full mode for release but disable for debug if incremental dexing is slower. Profile builds with Gradle Build Scan to identify bottlenecks (slow tasks, dependency resolution). Standardize JDK versions across the team. Use `assembleDebug` rather than `build` for daily development. Keep `local.properties` machine-specific settings out of version control.

```properties
org.gradle.caching=true
org.gradle.configuration-cache=true
org.gradle.parallel=true
org.gradle.jvmargs=-Xmx8192m -XX:+UseParallelGC
```

Read more: <https://developer.android.com/studio/build/optimize-your-build>

**Q84.** Describe your CI/CD pipeline—what checks run on PR, merge, and release builds? On PR: run unit tests, KtLint, Detekt, Android Lint, and compile checks (`assembleDebug`) within 10 minutes. Require passing status checks before merge. On merge to main: run integration tests (Hilt, Room, network fakes), screenshot tests, and generate coverage reports. Store artifacts. On release tag: run full E2E tests on Firebase Test Lab across device matrix, security scans (MobSF), ProGuard/R8 validation, and sign the APK/AAB with CI-managed keystore. Upload to Google Play Internal Testing track automatically. Run automated smoke tests via orchestrator on staged rollout. Use GitHub Actions, GitLab CI, or Bitrise with Docker images containing Android SDK and emulator snapshots. Parallelize test suites across runners. Notify Slack/Teams on failures. Maintain a rollback script to revert staged releases. Gate production promotion on crash-free rate metrics.

```yaml
# .github/workflows/pr.yml
jobs:
  check:
    steps:
      - uses: actions/checkout@v4
      - run: ./gradlew ktlintCheck detekt assembleDebugDebugUnitTest
```

Read more: <https://developer.android.com/studio/build/building-cmdline>

**Q85.** How do you automate app distribution to internal testers, beta users, and production? Use the Google Play Publishing API or Gradle Play Publisher plugin to upload AABs directly from CI to Play Console tracks (internal, alpha, beta, production). For internal testers, distribute via Firebase App Distribution with tester groups and release notes generated from commit messages. Automate versioning (`versionCode` from CI build number, `versionName` from Git tags). Use Fastlane for complex workflows (screenshots, metadata updates, phased releases) if Play API isn't sufficient. For enterprise distribution, host APKs on internal MDM or use private Google Play channels. Require QA sign-off before beta promotion via manual CI job triggers. Track rollout percentage programmatically and halt on crash threshold breaches. Use GitHub releases or GitLab packages for artifact archival. Automate Slack notifications to tester channels with download links. Maintain separate signing keys per track for security isolation.

```groovy
plugins {
    id 'com.github.triplet.play' version '3.8.4'
}
play {
    serviceAccountCredentials = file("credentials.json")
    track = "internal"
}
```

Read more: <https://developer.android.com/studio/publish>

**Q86.** What is your approach to versioning and release management in a fast-paced delivery cycle? Use semantic versioning (`MAJOR.MINOR.PATCH`) where `MAJOR` indicates breaking changes, `MINOR` features, `PATCH` fixes. Automate `versionCode` as monotonically increasing integer (CI build number or Git commit count). Tag releases in Git with `vX.Y.Z` to trigger release pipelines. Maintain a `CHANGELOG.md` enforced via PR templates. Use trunk-based development with short-lived feature flags rather than long release branches. Release weekly or bi-weekly with automated canary (1%) → staged (10% → 50%) → full rollout via Play Console API. Hotfix critical bugs by cherry-picking to a release branch and fast-tracking through internal testing. Coordinate backend API versioning with mobile releases. Use feature flags to decouple app store releases from feature launches. Monitor crash rates and ANRs via Firebase for 24 hours before increasing rollout percentage. Document rollback procedures.

```groovy
android {
    defaultConfig {
        versionCode = System.getenv("GITHUB_RUN_NUMBER")?.toInt() ?: 1
        versionName = "2.4.${versionCode}"
    }
}
```

Read more: <https://developer.android.com/studio/publish>

**Q87.** How do you handle build failures caused by transitive dependency conflicts? Use `./gradlew app:dependencies --configuration implementation` to visualize the dependency tree and identify conflicting versions. Force resolution via `resolutionStrategy` in `build.gradle.kts`: `force("org.json:json:20231013")` or `substitute` rules. Use `strictly` in version catalogs to enforce exact versions. Exclude conflicting transitive modules with `exclude(group, module)` when they're pulled in by multiple libraries. Use `dependencyInsight` to trace why a specific version is selected. Align versions using BOMs (e.g., `compose-bom`, `kotlinx-coroutines-bom`). For native dependencies (`.so` files), ensure only one module packages the library to avoid runtime linkage errors. Document resolved conflicts in a `DEPENDENCIES.md` file. Run `./gradlew buildHealth` with the Dependency Analysis plugin to detect unused dependencies that might cause conflicts. Test thoroughly after forcing versions to ensure compatibility.

```kotlin
configurations.all {
    resolutionStrategy {
        force("org.json:json:20231013")
        cacheChangingModulesFor(0, "seconds")
    }
}
```

Read more: <https://developer.android.com/studio/build/dependencies>

**Q88.** What static analysis tools do you enforce—Detekt, KtLint, Android Lint, custom rules? Enforce KtLint for formatting (trailing commas, indentation, import ordering) integrated via `ktlint-gradle` plugin with `autoCorrect` on CI. Use Detekt for code smell detection (complexity, magic numbers, coroutine usage) with a custom config file (`detekt.yml`) tuned to team standards. Use Android Lint for framework-specific issues (missing translations, unsafe `Intent` usage, performance bugs). Write custom Detekt rules for architecture violations (e.g., "ViewModels must not import `android.content.Context`") and custom Lint rules for project-specific patterns. Run all three on every PR via GitHub Actions; block merge on failures. Generate HTML/SARIF reports for review. Suppress false positives with annotations (`@Suppress`) requiring PR justification. Track trend lines (violations over time) in CI dashboards. Run `detektBaseline` only temporarily during large refactorings, not as a permanent escape hatch.

```kotlin
// Custom Detekt rule
class ViewModelContextRule : Rule("ViewModelContext") {
    override fun visitImportExpression(expression: KtImportExpression) {
        if (expression.importPath?.pathStr?.contains("android.content.Context") == true) {
            report(CodeSmell(issue, Entity.from(expression), "ViewModels must not depend on Context"))
        }
    }
}
```

Read more: <https://developer.android.com/studio/write/lint>

**Q89.** How do you manage secrets and signing configurations in CI without exposing them in repository history? Store signing keystores as base64-encoded secrets in CI environment variables (GitHub Secrets, GitLab CI/CD Variables, or AWS Secrets Manager) rather than in the repo. Inject them during CI by decoding into temporary files before the build step, then delete immediately after signing. Use `local.properties` for local development secrets (ignored by Git) and a stub file (`local.properties.example`) for documentation. For Google Play API access, use JSON key files stored as CI secrets. Rotate secrets regularly and audit access logs. Use Gradle's `secrets-gradle-plugin` to inject API keys into `BuildConfig` at build time from environment variables, not hardcoded. Enable branch protection so only authorized users can run release workflows. Scan repository history with `git-secrets` or TruffleHog to ensure no credentials were committed accidentally. Use short-lived tokens where possible.

```yaml
# CI step
- name: Decode keystore
  run: echo "${{ secrets.SIGNING_KEY }}" | base64 -d > app/keystore.jks
- run: ./gradlew assembleRelease
- run: rm app/keystore.jks
```

Read more: <https://developer.android.com/studio/publish/app-signing>

**Q90.** Describe how you would set up a modular build system where features can be developed and tested in isolation. Structure the project with `app`, `core`, `feature:*`, and `test` modules. Each feature module contains `api` (public interface) and `implementation` (internal code) submodules or uses `internal` visibility to hide internals. Feature modules depend only on `core` and other feature `api` modules, never on implementation details. Provide a `demo` application module per feature (`:feature:login:demo`) that launches just that feature's entry point for isolated development and testing. Use Hilt's `TestInstallIn` to replace dependencies in feature tests. Define module boundaries via Gradle's `implementation` vs `api` strictly. Use the Dependency Analysis Gradle Plugin to detect leaks across boundaries. Set up CI to build and test only changed modules and their dependents (`--changed-projects`) for speed. Document module dependency rules in `ARCHITECTURE.md`. Enforce via custom Detekt rules or Gradle dependency checks.

```kotlin
// :feature:login:demo build.gradle.kts
plugins { id("com.android.application") }
dependencies {
    implementation(project(":feature:login:api"))
    implementation(project(":feature:login:impl"))
    implementation(project(":core"))
}
```

Read more: <https://developer.android.com/topic/modularization>

***

### Leadership, Team & Process

**Q91.** How do you onboard a junior developer and bring them up to speed on your team's architecture? Provide a structured onboarding checklist spanning environment setup, codebase walkthroughs, and architecture documentation. Pair them with a buddy mentor for the first month. Start with small, well-scoped tickets touching one layer (e.g., a UI mapper or a UseCase) to build confidence. Walk through the architecture diagram explaining data flow from UI to network and back. Have them shadow code reviews before submitting their own. Assign a "first feature" that spans multiple layers but is low-risk, with frequent check-ins. Share recorded architecture deep-dive sessions. Encourage questions in a dedicated Slack channel. Review their first PRs with extensive educational comments rather than just corrections. Set up a local sandbox where they can experiment without production consequences. Measure progress by their ability to independently complete a vertical slice feature within 6-8 weeks.

```markdown
## Onboarding Week 1
- [ ] Clone repo, run app, pass pre-commit hooks
- [ ] Read ADR-001 (Architecture Overview)
- [ ] Pair with mentor on "Good first issue": add analytics event
- [ ] Shadow 2 code reviews
```

Read more: <https://developer.android.com/topic/architecture>

**Q92.** Describe a time you had to push back on a product requirement due to technical constraints—how did you handle it? When product requested real-time collaborative editing with offline support for a note-taking feature, I explained the complexity of operational transformation (OT) algorithms, conflict resolution, and the 6-month engineering timeline. I presented alternatives: optimistic locking with last-write-wins for MVP, delivering in 6 weeks, then iterating toward true collaboration. I framed the discussion around user value versus engineering cost, showing data that 90% of users don't need simultaneous editing. I proposed a phased roadmap with clear milestones and success metrics. I involved engineering leadership to align on trade-offs. We agreed to the simplified MVP with a commitment to revisit real-time collaboration if user feedback demanded it. The key was providing solutions, not just objections, and speaking in product's language (user impact, timeline, risk).

```kotlin
// MVP: Simple last-write-wins sync
suspend fun saveNote(note: Note) {
    dao.save(note.copy(syncStatus = PENDING, modifiedAt = System.currentTimeMillis()))
    enqueueSync()
}
```

Read more: <https://developer.android.com/topic/product-management>

**Q93.** How do you balance shipping features quickly with maintaining long-term code health? Allocate 20% of sprint capacity to refactoring, test coverage, and technical debt reduction alongside feature work. Require that every feature PR includes tests and documentation updates. Use feature flags to ship incomplete features safely without blocking the codebase. Enforce architecture standards via code review so shortcuts don't accumulate. Track technical debt in a visible backlog with business impact estimates. When under pressure, accept tactical debt consciously—document it with a TODO and a ticket. Automate quality checks (lint, coverage) so they don't slow down developers. Break large features into incremental PRs that improve the codebase as they go (e.g., refactoring before adding logic). Measure code churn and complexity trends; intervene when metrics degrade. Communicate to stakeholders that sustained velocity requires maintenance investment.

```markdown
## Sprint Planning
- Feature work: 80% capacity
- Tech debt / Refactoring: 20% capacity
- Rule: Every PR must include tests and ADR updates if architectural
```

Read more: <https://developer.android.com/topic/architecture>

**Q94.** What is your approach to resolving technical disagreements within the team? Facilitate a decision-making framework: define the problem, list options with pros/cons, and evaluate against agreed criteria (performance, maintainability, timeline, team knowledge). If opinions are split, request a time-boxed spike (1-2 days) where proponents build minimal prototypes to validate assumptions. Use architecture decision records (ADRs) to document the outcome and rationale, so dissent is recorded respectfully. As a lead, I don't dictate but guide the team toward consensus; if consensus is impossible, I make the call and take responsibility. Ensure the decision is reversible if new information emerges. Follow up post-implementation to validate the choice. Keep discussions technical, not personal. If a disagreement stems from knowledge gaps, arrange a learning session. Document patterns in team wiki to prevent recurring debates.

```markdown
## Decision Log: State Management
- Options: LiveData vs StateFlow
- Criteria: Coroutine integration, Compose support, Testing
- Decision: StateFlow for new code, LiveData grandfathered
- Reversible: Yes, migration path documented
```

Read more: <https://developer.android.com/topic/architecture>

**Q95.** How do you mentor developers who are resistant to adopting new patterns like Compose or coroutines? Start with empathy—understand if resistance stems from fear, past bad experiences, or workload pressure. Demonstrate value with side-by-side comparisons: show how coroutines reduce callback hell or how Compose previews speed up UI iteration. Pair program on a small feature using the new pattern, letting them drive while you navigate. Provide internal workshops with hands-on labs, not just presentations. Create a "safe" feature branch or demo module for experimentation without production risk. Share success metrics from early adopters (fewer bugs, faster delivery). Avoid mandating immediate wholesale adoption; allow gradual migration with hybrid approaches. Address specific concerns directly (e.g., "Will this break our existing tests?" → show testing strategy). Recognize and celebrate first successes publicly. If fundamental skill gaps exist, fund external training or conference attendance.

```kotlin
// Side-by-side comparison for mentoring
// Old: RxJava
disposable.add(repo.getData().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe { update(it) })
// New: Coroutines
viewModelScope.launch { update(repo.getData()) }
```

Read more: <https://developer.android.com/jetpack/compose>

**Q96.** Describe how you would plan the migration of a legacy Java codebase to Kotlin while keeping the app shippable. Migrate incrementally file-by-file, starting with test files and data classes to build team confidence. Use Android Studio's automatic Java-to-Kotlin converter for boilerplate, then manually refine idioms. Establish a "Kotlin-first" rule: new code must be Kotlin; old Java is migrated when touched. Configure Gradle with `apply plugin: 'kotlin-android'` and interop settings. Ensure CI runs both Java and Kotlin tests without disruption. Migrate layer by layer: data/models first (safest), then repositories, then ViewModels, finally UI. Maintain binary compatibility—Kotlin compiles to JVM bytecode interoperable with Java. Use `@JvmName` and `@JvmStatic` annotations where Java callers need specific signatures. Run static analysis on both languages. Don't rewrite working logic unnecessarily; focus on classes with high churn or bug rates. Time-box migration work (e.g., 2 files per sprint). Celebrate milestones to maintain morale.

```kotlin
// Interop-friendly Kotlin
@JvmName("getUsersList")
fun getUsers(): List<User> = dao.getAll()

// Java calls: RepositoryKt.getUsersList()
```

Read more: <https://developer.android.com/kotlin>

**Q97.** How do you ensure knowledge is distributed and no single developer is a bottleneck? Enforce pair programming or mob reviews for critical architectural changes so at least two people understand every major system. Require code review approval from someone outside the author's usual domain to spread familiarity. Maintain runbooks for deployment, incident response, and environment setup. Rotate on-call responsibilities and feature ownership every quarter. Document architecture decisions (ADRs) and complex algorithms in the team wiki. Conduct regular "lunch and learn" sessions where developers present their recent work. Avoid assigning the same person to the same feature area repeatedly; deliberately mix assignments. Use a shared Slack channel for questions rather than DMs so answers are visible. Mentor junior developers explicitly toward ownership roles. If a bottleneck emerges, immediately pair the expert with another developer on the next related task.

```markdown
## Knowledge Sharing Calendar
- Mondays: Architecture Deep Dive (rotating presenter)
- Fridays: Code Review Lottery (review outside your domain)
- Quarterly: On-call rotation + Runbook updates
```

Read more: <https://developer.android.com/topic/architecture>

**Q98.** What is your process for evaluating new libraries or architectural patterns before team-wide adoption? Define evaluation criteria: maintenance status, community size, license compatibility, APK size impact, build time cost, and learning curve. Build a prototype in a feature branch or demo app integrating the library with our stack (DI, networking, threading). Measure performance benchmarks and binary size delta with APK Analyzer. Audit the library's dependencies for transitive bloat or security risks. Check GitHub issues for unresolved critical bugs and release cadence. Require two team members to review the prototype code and documentation. Run a team discussion or RFC (Request for Comments) document collecting concerns. If approved, migrate one low-risk feature first as a pilot. Monitor crash rates and build stability for one release cycle before broader rollout. Document the decision in an ADR. Maintain a policy to revisit evaluations annually; deprecate libraries that become unmaintained.

```markdown
## RFC-014: Adopt Koin over Hilt for new SDK
- Criteria: Compile safety, Testing ease, Multiplatform, Community
- Prototype: feature/auth branch
- Reviewers: @alice, @bob
- Pilot: SDK module only
```

Read more: <https://developer.android.com/jetpack>

**Q99.** How do you communicate technical debt and its impact to non-technical stakeholders? Translate technical debt into business metrics they understand: "This legacy module causes 30% of our production crashes, affecting user retention" or "Refactoring the checkout flow will reduce page load time by 2 seconds, increasing conversion by X%." Use visual dashboards showing crash rates, build times, or deployment frequency trends. Frame debt as risk: "If we don't update this library, we lose Play Store compliance in 3 months." Propose concrete trade-offs: "We can ship Feature A in 2 weeks with shortcuts, or 4 weeks with proper foundation that enables Features B and C." Avoid jargon; use analogies like "paying interest on a loan." Include debt items in product roadmaps with estimated user impact. Secure dedicated sprint time (20% rule) by showing historical data that teams with zero maintenance time eventually halt entirely.

```markdown
## Debt Report Q2
- Checkout legacy module: 30% of crashes, 4.2★ rating impact
- Proposed fix: 3 sprints, reduces crash rate by 25%
- ROI: Prevents churn of ~2K users/month
```

Read more: <https://developer.android.com/topic/product-management>

**Q100.** How do you handle a critical production crash when the root cause is unclear and pressure is high? First, mitigate user impact: use feature flags or remote config to disable the crashing feature immediately if possible. Roll back the release via Play Console if the crash is tied to a recent update. Gather data: analyze Crashlytics stack traces, device models, OS versions, and user actions leading to the crash. Reproduce on the exact device/OS combination if available. If unclear, add targeted logging or use a custom `UncaughtExceptionHandler` to capture more context in a hotfix. Form a war room with relevant engineers (Android, backend, QA) to parallelize investigation. Communicate transparently to stakeholders: "We are investigating, feature X is disabled, ETA for fix is Y hours." Avoid guessing fixes; validate hypotheses with reproduction. Once fixed, write a postmortem documenting root cause, detection lag, and prevention measures. Prioritize adding automated tests for the failure path.

```kotlin
// Emergency feature flag kill switch
if (!remoteConfig.getBoolean("checkout_v2_enabled")) {
    startActivity(Intent(this, LegacyCheckoutActivity::class.java))
    return
}
```

Read more: <https://developer.android.com/topic/performance>

***

### System Design & Cross-Functional

**Q101.** Design a real-time chat feature that supports offline messaging, media sharing, and end-to-end encryption. Use WebSocket for real-time message delivery with automatic reconnection and heartbeat pings. Queue outgoing messages in Room with status `PENDING`; sync via WorkManager when offline. For media, upload to encrypted object storage (S3 with SSE) generating pre-signed URLs; share URL + decryption key in the message payload. Implement E2E encryption using the Signal Protocol or libsodium with per-session keys exchanged via X3DH. Store private keys in Android Keystore. Use `MessagePack` or protobuf for compact wire format. Display messages via Paging 3 from local DB as source of truth. Handle read receipts and typing indicators as lightweight ephemeral events. Use `WorkManager` for guaranteed media upload retry. Implement key rotation periodically. Backup encrypted message history to user-controlled cloud storage. Test with multiple devices and airplane mode toggling.

```kotlin
@Entity
data class Message(
    @PrimaryKey val id: String = UUID.randomUUID().toString(),
    val text: String,
    val status: SendStatus = SendStatus.PENDING,
    val encryptedPayload: ByteArray
)
```

Read more: <https://developer.android.com/guide/topics/connectivity>

**Q102.** How would you architect an app that needs to support both phone and tablet form factors with shared code? Use a single activity with Navigation Component and responsive layouts via `WindowSizeClass` (Compose) or resource qualifiers (`sw600dp`, `sw720dp`). Define a `WindowState` object computed from `WindowMetrics` that drives UI layout decisions (single pane vs dual pane). Use `SlidingPaneLayout` or Compose `NavSuiteScaffold` for list-detail patterns. Share all ViewModels and business logic; only the UI layer adapts to screen size. Use `LazyVerticalGrid` or `LazyStaggeredGrid` for content that reflows. Implement foldable support with `WindowInfoTracker` to detect posture (flat, half-opened). Test on emulators with varying screen sizes and physical tablets. Avoid separate phone/tablet modules—use responsive composables and alternate layout files. Use `ConstraintLayout` or `BoxWithConstraints` for precise adaptive positioning. Maintain one navigation graph with conditional destinations based on form factor.

```kotlin
@Composable
fun AdaptiveScreen(windowSize: WindowSizeClass) {
    when (windowSize.widthSizeClass) {
        WindowWidthSizeClass.Compact -> PhoneLayout()
        WindowWidthSizeClass.Medium, WindowWidthSizeClass.Expanded -> TwoPaneLayout()
    }
}
```

Read more: <https://developer.android.com/guide/topics/large-screens>

**Q103.** Design a navigation and routing system for an app with 50+ screens and deep linking requirements. Use the Navigation Component with nested navigation graphs per feature module. Define a `core-navigation` module containing route constants and deep link URI contracts as sealed classes. Each feature module exposes its graph via a `NavigationNode` interface registered in the app module's `NavHost`. Use type-safe navigation with Kotlin DSL generated routes to prevent stringly-typed errors. Handle deep links by defining URI patterns in each graph; use a trampoline activity to validate authentication or feature flags before routing. For conditional flows (onboarding, paywalls), use a `Navigator` interface that intercepts destinations and redirects if conditions aren't met. Maintain a back stack policy per graph (e.g., auth graph pops to root on success). Use `BottomNavigationView` or `NavigationRail` with `MultipleBackStacks` to preserve state across tabs. Document the routing map in a Mermaid diagram.

```kotlin
sealed class Route(val path: String) {
    object Home : Route("home")
    data class Detail(val id: String) : Route("detail/{id}") {
        fun createPath() = "detail/$id"
    }
}
```

Read more: <https://developer.android.com/guide/navigation>

**Q104.** How would you build a SDK that third-party developers integrate into their apps—what constraints matter most? Minimize transitive dependencies to avoid version conflicts with host apps; shade or repackage critical libraries if necessary. Keep the public API surface small and stable—every public class is a contract you can't break without major version bumps. Use semantic versioning strictly. Provide clear integration documentation with code samples and a demo app. Support minSdk that aligns with market data but don't lag too far behind. Handle lifecycle automatically using `LifecycleObserver` rather than requiring manual init/shutdown. Use weak references to host `Activity`/`Context` to prevent memory leaks. Respect the host app's theme and threading model; never assume main thread availability for callbacks. Provide ProGuard/R8 consumer rules in the AAR. Include an opt-out analytics mechanism. Test in popular host apps (conflict scenarios). Offer Maven Central distribution with POM metadata.

```kotlin
class MySdk private constructor(context: Context) {
    companion object {
        @Volatile private var instance: MySdk? = null
        fun init(context: Context) = instance ?: synchronized(this) {
            instance ?: MySdk(context.applicationContext).also { instance = it }
        }
    }
    fun track(event: String) { /* weak ref to listeners */ }
}
```

Read more: <https://developer.android.com/studio/projects/android-library>

**Q105.** Describe the architecture for an app that streams video with adaptive bitrate and offline download support. Use ExoPlayer with `DashMediaSource` or `HlsMediaSource` for adaptive streaming via DASH/HLS manifests. Implement `TrackSelector` to choose streams based on bandwidth meter (`DefaultBandwidthMeter`) and device capabilities. For offline, use ExoPlayer's `DownloadManager` with `DownloadService` to cache segments in a `SimpleCache` backed by `CacheDataSource`. Expose download progress via `DownloadManager.Listener` to UI. Use `WorkManager` to schedule large downloads during charging/WiFi. Store DRM licenses (Widevine) via `OfflineLicenseHelper` for protected content. Use `MediaSession` for background playback and integration with Android Auto/Wear. Implement a `DataSource` factory that switches between network and cache transparently. Handle network switches by re-evaluating tracks. Provide quality selection UI overriding automatic selection. Test adaptive behavior with network link conditioner throttling bandwidth.

```kotlin
val downloadManager = DownloadManager(context, databaseProvider, cache)
val downloadRequest = DownloadRequest.Builder(videoId, videoUri).build()
DownloadService.sendAddDownload(context, MyDownloadService::class.java, downloadRequest, false)
```

Read more: <https://developer.android.com/media>

**Q106.** How would you design a feature flag system that works across Android, iOS, and backend? Use a centralized feature flag platform (LaunchDarkly, Firebase Remote Config, or custom) with REST/gRPC APIs serving flag configurations. Define flags in a shared schema (JSON or protobuf) consumed by all platforms. Cache flag values locally with TTL to ensure offline functionality. Use flag evaluation in the domain layer, not UI, to keep logic consistent. Implement user targeting (percentage rollout, user segments, device attributes) server-side. Provide a debug panel in internal builds to override flags for QA. Use consistent naming conventions (`feature.checkout_v2_enabled`). Ensure flags are type-safe by generating platform-specific accessors from the shared schema. Implement kill switches that disable features without app updates. Audit flag usage to remove stale flags after rollout completion. Coordinate backend API versioning with flag states to prevent mismatched behavior.

```kotlin
object FeatureFlags {
    val checkoutV2: Boolean get() = remoteConfig.getBoolean("feature.checkout_v2_enabled")
    val maxItems: Int get() = remoteConfig.getLong("feature.max_cart_items").toInt()
}
```

Read more: <https://developer.android.com/topic/architecture>

**Q107.** Design a local-first architecture where the app remains fully functional without network for days. Use Room as the single source of truth with a sync layer abstracted behind repository interfaces. Implement CRDTs (Conflict-free Replicated Data Types) or operational transformation for collaborative data, or use timestamp-based last-write-wins for single-user data. Queue all mutations in a `pending_changes` table with `WorkManager` constraints (network required) for background sync. Optimistically apply changes to UI immediately. Use `Flows` from Room to automatically update UI when local data changes. Implement incremental sync with `sync_tokens` to minimize data transfer when reconnecting. Handle conflict resolution automatically with server-wins for critical data and client-wins for user preferences. Provide visual indicators (sync status icons) for pending changes. Cap local storage to prevent unbounded growth; archive old data. Test by enabling airplane mode for extended periods and verifying all features work.

```kotlin
@Dao
interface ChangeDao {
    @Insert
    suspend fun queue(change: PendingChange)

    @Query("SELECT * FROM pending_changes ORDER BY timestamp")
    fun observePending(): Flow<List<<PendingChange>>
}
```

Read more: <https://developer.android.com/topic/architecture/data-layer>

**Q108.** How would you approach building a white-label Android app framework used by multiple brands? Create a core module containing all business logic, networking, and architecture; expose theming and configuration via an interface. Each brand module depends on core and provides brand-specific resources (colors, logos, fonts), API endpoints, and feature toggles via `productFlavors` or separate application modules. Use Gradle build variants to generate distinct APKs/AABs per brand from shared code. Inject brand configuration at runtime via a `BrandConfiguration` object loaded from assets or metadata. Use `AndroidManifest` placeholders for app name, icon, and authorities. Keep brand-specific code minimal—ideally just resources and a configuration class. Use `resValue` and `buildConfigField` for compile-time brand constants. Provide a white-label SDK approach if brands need to integrate into their own apps. Maintain a brand matrix CI job that builds all variants. Document customization boundaries to prevent core code divergence.

```kotlin
data class BrandConfig(
    @ColorRes val primaryColor: Int,
    val apiEndpoint: String,
    val features: Set<<Feature>
)

class BrandApp : Application() {
    override fun onCreate() {
        super.onCreate()
        val config = BrandConfigLoader.load(this, BuildConfig.BRAND)
        initDI(config)
    }
}
```

Read more: <https://developer.android.com/studio/build>

**Q109.** Describe how you would design an analytics pipeline that respects user privacy while providing product insights. Collect only anonymized event data with no PII; use hashed device IDs rather than advertising IDs where possible. Implement consent management requiring explicit opt-in before initializing analytics SDKs. Batch events locally and transmit over HTTPS with certificate pinning. Provide an in-app privacy dashboard showing collected data categories and allowing deletion. Use differential privacy techniques or aggregation to prevent individual user identification in reports. Store events in a local queue (Room) and flush via WorkManager to respect battery and network constraints. Support "do not track" signals and regional regulations (GDPR, CCPA). Audit third-party analytics libraries for unauthorized data collection. Use first-party analytics infrastructure (self-hosted) instead of third-party when privacy is paramount. Document data retention policies and auto-delete old local logs. Make analytics sampling configurable to reduce data volume.

```kotlin
class PrivacyFirstAnalytics @Inject constructor(private val dao: EventDao, private val dispatcher: CoroutineDispatcher) {
    fun log(event: AnalyticsEvent) {
        if (!consentManager.canTrack(event.category)) return
        scope.launch(dispatcher) { dao.insert(event.toEntity()) }
    }
}
```

Read more: <https://developer.android.com/privacy-and-security>

**Q110.** How would you architect an Android app that integrates with a complex existing backend built for web-first? Create an anti-corruption layer (adapter pattern) in the data layer that maps web-centric DTOs (deep nesting, snake\_case, generic wrappers) to mobile-friendly domain models (flat, camelCase, typed). Use mappers to transform paginated web responses into Paging 3 sources. Handle web-specific auth (cookies, CSRF tokens) by storing them in `CookieJar` and injecting headers via OkHttp interceptors. Adapt web error formats (HTML error pages, non-JSON) into domain exceptions. If the backend sends large payloads, implement field filtering or separate mobile-optimized endpoints via BFF (Backend-for-Frontend) pattern. Cache aggressively on mobile to compensate for web-optimized API chattiness. Use GraphQL as an intermediary if the backend supports it, allowing mobile to request precise data shapes. Maintain API contract tests to detect backend changes early. Document mobile-specific requirements for backend teams.

```kotlin
@JsonClass(generateAdapter = true)
data class WebUserResponse(
    @Json(name = "user_data") val userData: UserData,
    @Json(name = "meta") val meta: Meta
)

fun WebUserResponse.toDomain() = User(
    id = userData.id,
    name = userData.profile.displayName
)
```

Read more: <https://developer.android.com/topic/architecture/data-layer>

***

### Emerging Tech & Future-Proofing

**Q111.** What is your assessment of Kotlin Multiplatform for sharing code between Android and iOS in production? KMP is production-ready for business logic (domain models, UseCases, validation) but still maturing for UI sharing (Compose Multiplatform). Share `commonMain` modules for networking, serialization, and data repositories using Ktor and kotlinx.serialization. Expect platform-specific implementations (`actual` declarations) for keychain (iOS) vs Keystore (Android), local storage (Room vs CoreData wrappers), and platform channels. Build times and IDE support have improved significantly but require Gradle expertise. The main risk is team composition—iOS developers must be comfortable with Kotlin and Gradle. For teams with strong native iOS UI skills, share only domain layer. For teams wanting maximum reuse, Compose Multiplatform offers shared UI but with native look-and-feel trade-offs. Evaluate based on team size, release cadence parity, and whether the shared logic is complex enough to justify the abstraction overhead.

```kotlin
// commonMain
expect class Platform() {
    val name: String
}
// androidMain
actual class Platform actual constructor() {
    actual val name: String = "Android ${android.os.Build.VERSION.SDK_INT}"
}
```

Read more: <https://kotlinlang.org/docs/multiplatform.html>

**Q112.** How do you evaluate the maturity of Jetpack Compose for replacing an entire existing UI layer? Compose is mature for new projects and incremental adoption; full replacement depends on app complexity and team readiness. Evaluate: 1) Library ecosystem—are all critical dependencies (maps, charts, video players) available as Compose-friendly APIs or wrappers? 2) Performance—profile recomposition on low-end devices with your specific UI complexity. 3) Team expertise—ensure developers understand state hoisting and side effects. 4) Accessibility—verify TalkBack and keyboard navigation meet requirements. 5) Testing—confirm screenshot and UI test infrastructure supports Compose. For established apps, migrate screen-by-screen starting with new features or simple settings screens. Maintain hybrid architecture with `ComposeView` and `AndroidView` interop during transition. Full replacement is viable if the app is medium-sized, the team is trained, and the release cycle allows a 3-6 month migration window with feature freeze on legacy UI.

```kotlin
class LegacyFragment : Fragment() {
    override fun onCreateView(...) = ComposeView(requireContext()).apply {
        setContent { MaterialTheme { MyComposeScreen() } }
    }
}
```

Read more: <https://developer.android.com/jetpack/compose>

**Q113.** What is your understanding of Android's declarative UI evolution beyond Compose—like Glance for widgets? Glance applies Compose's declarative paradigm to App Widgets, generating `RemoteViews` under the hood while exposing a type-safe Kotlin API. It simplifies widget development and reduces boilerplate compared to `RemoteViews`. Beyond widgets, Android is exploring declarative system UI and large-screen adaptations via `WindowSizeClass` and adaptive layouts. The trend is toward state-driven UI where the framework handles rendering differences across form factors. Wear OS and TV are adopting Compose-based APIs (`WearCompose`, `Compose for TV`). The evolution suggests a unified declarative toolkit across surfaces, though each maintains surface-specific constraints (widgets lack interactivity, Wear has circular layouts). Developers should expect to write business logic once and adapt UI declarations per surface. Invest in learning Compose fundamentals as they transfer across these emerging APIs.

```kotlin
class MyWidget : GlanceAppWidget() {
    override suspend fun provideGlance(context: Context, id: GlanceId) {
        provideContent {
            Column {
                Text("Hello Widget")
                Button(text = "Refresh", onClick = actionRunCallback<<RefreshAction>())
            }
        }
    }
}
```

Read more: <https://developer.android.com/jetpack/glance>

**Q114.** How do you stay current with Android platform changes and decide when to adopt new APIs? Follow official channels: Android Developers Blog, Android Developers YouTube, Google I/O sessions, and the AndroidX release notes. Subscribe to the "Now in Android" newsletter and the AOSP issue tracker for deep technical changes. Evaluate new APIs via a "innovation time" spike: build a prototype in a sandbox branch measuring stability, performance, and team learning curve. Adopt stable APIs (not alpha/beta) for production unless they solve critical blockers. Use Android Studio's "New Project" templates and API diff reports to understand changes. Maintain a tech radar document categorizing technologies into Adopt, Trial, Assess, Hold. Involve the team in quarterly architecture reviews to discuss platform updates. Balance early adoption (competitive advantage, Google support) against stability risks. Target latest `targetSdk` within 6 months of release to meet Play Store requirements, but feature adoption is prioritized by user value.

```markdown
## Tech Radar Q2 2026
- Adopt: Baseline Profiles, KSP, Compose
- Trial: Generative AI (ML Kit)
- Assess: XR SDK
- Hold: Dynamic code loading
```

Read more: <https://developer.android.com/studio/releases>

**Q115.** What is your view on large language models or on-device AI integration in Android apps? On-device AI via TensorFlow Lite, MediaPipe, or Android's Gemini Nano offers privacy (data stays local), offline functionality, and low latency for specific tasks (text classification, image segmentation, smart reply). However, model size impacts APK and memory; use dynamic feature delivery for large models. LLMs on-device are emerging but limited by device capability; most apps should use cloud LLMs with strict data handling policies. Integrate AI as a feature enhancement, not core architecture dependency—ensure the app works if AI fails or is unavailable. Use `MlKit` for ready-made solutions (OCR, translation) rather than custom model training unless you have ML engineers. Respect user consent for AI-processed data. Monitor battery and thermal impact of sustained model inference. The trend is hybrid: lightweight on-device inference for real-time tasks, cloud for complex generative tasks.

```kotlin
val options = TranslatorOptions.Builder()
    .setSourceLanguage(TranslateLanguage.ENGLISH)
    .setTargetLanguage(TranslateLanguage.SPANISH)
    .build()
val translator = Translation.getClient(options)
translator.translate(text).addOnSuccessListener { result -> updateUi(result) }
```

Read more: <https://developer.android.com/ml>

**Q116.** How do you approach foldable and large-screen device support from an architectural standpoint? Architecture should be UI-layer agnostic to form factor. Use `WindowSizeClass` and `WindowInfoTracker` to drive layout decisions at the composition level, not in ViewModels. ViewModels expose state; UI decides whether to render single-pane, two-pane, or dual-screen layouts. Use `SlidingPaneLayout` or `NavSuiteScaffold` for list-detail patterns that adapt automatically. Handle fold posture (flat, half-opened, tabletop) via `Jetpack WindowManager` to adjust layouts (e.g., video on top, controls on bottom when folded). Test with emulators supporting foldable presets and physical devices (Samsung Galaxy Z, Pixel Fold). Ensure drag-and-drop works across screens using `DropHelper`. Don't maintain separate codebases for foldables; responsive design principles cover phones, tablets, and foldables. Use `resizableActivity="true"` and `maxAspectRatio` correctly in the manifest. Architect state to survive configuration changes during folding/unfolding.

```kotlin
val windowInfo = WindowInfoTracker.getOrCreate(context).windowLayoutInfo(activity)
windowInfo.collectIn(activity) { info ->
    val posture = info.displayFeatures.filterIsInstance<FoldingFeature>().firstOrNull()?.state
    updateLayout(posture)
}
```

Read more: <https://developer.android.com/guide/topics/large-screens>

**Q117.** What role do you see for Baseline Profiles and Macrobenchmark in your performance strategy? Baseline Profiles ship pre-compiled hot path data in the APK, allowing ART to optimize app startup and critical user journeys immediately rather than relying on JIT over days. They reduce cold start time by 20-40% on supported devices. Macrobenchmark measures real-world startup, scrolling, and animation performance with statistical significance, producing actionable trace files. Together they form a data-driven performance loop: benchmark current state → identify jank via traces → optimize code → write Baseline Profile rules for the improved paths → re-benchmark to validate. Integrate Macrobenchmark in CI to detect regressions on release builds. Generate Baseline Profiles using the Baseline Profile Gradle plugin and `Macrobenchmark` library. Update profiles with each significant release. These are essential for competitive apps where startup time directly impacts user acquisition and Play Store ranking.

```kotlin
@OptIn(ExperimentalBaselineProfilesApi::class)
@BaselineProfileRule
class StartupBenchmark {
    @Test fun startup() = baselineRule.collectBaselineProfile(packageName) {
        pressHome(); startActivityAndWait()
    }
}
```

Read more: <https://developer.android.com/topic/performance/baselineprofiles>

**Q118.** How do you think about Wear OS, TV, or Auto development in relation to your mobile app strategy? Treat them as specialized surfaces consuming the same domain layer and business logic via shared modules, but with distinct UI layers optimized for input modality and context. Wear OS needs glanceable, glanceable interactions—use complications and tiles with minimal data from the mobile app's repository layer. TV requires D-pad navigation, leanback UI, and audio focus handling; share media playback logic via `Media3` but build separate fragments. Auto uses Android Automotive OS or Android Auto with `MediaBrowserService` for audio apps; again, shared media domain logic. Don't port mobile UI directly; redesign for the surface. Use dynamic feature modules or separate app modules to deliver surface-specific APKs from shared code. Maintain a core module with networking, auth, and data that all surfaces depend on. Prioritize surfaces based on user analytics and business goals.

```kotlin
// core module shared across mobile, wear, tv
class MediaRepository @Inject constructor(private val api: MediaApi) {
    fun getPlaylist(): Flow<List<<Track>> = api.observePlaylist()
}
```

Read more: <https://developer.android.com/training/wearables>

**Q119.** What is your experience with server-driven UI frameworks, and when are they appropriate? Server-driven UI (SDUI) allows the backend to control layout and components via JSON/XML schemas rendered natively. Appropriate for: rapidly changing UI (marketing screens, dashboards), A/B testing layouts without app releases, or apps with highly regional customization. Frameworks like Airbnb's Epoxy, LayoutDSLs, or custom JSON-to-Compose parsers enable this. Risks include: increased payload size, latency waiting for UI definitions, limited offline capability, and difficulty handling complex interactions. Security concerns arise if the server sends executable-like logic. Use SDUI for static or lightly interactive content; avoid it for core transactional flows requiring reliability and speed. Maintain a native component library that the server references by name/type. Cache schema definitions locally. Validate schemas with strong typing. Monitor parse failures and fallback to default layouts. SDUI is a tool for specific velocity problems, not a universal architecture.

```kotlin
@Composable
fun ServerDrivenLayout(schema: Schema) {
    when (schema.type) {
        "column" -> Column { schema.children.forEach { ServerDrivenLayout(it) } }
        "text" -> Text(schema.props["text"] ?: "")
        "button" -> Button(onClick = { /* handle action */ }) { Text(schema.props["label"] ?: "") }
    }
}
```

Read more: <https://developer.android.com/jetpack/compose/layouts/adaptive>

**Q120.** How would you prepare your team's architecture for potential future platform shifts or form factors? Maximize framework-agnostic business logic in Kotlin multiplatform or pure Kotlin modules. Isolate Android-specific APIs (lifecycle, notifications, sensors) behind interfaces in a platform layer so they can be reimplemented. Use dependency injection extensively to swap implementations. Design UI state as platform-neutral models; only the presentation layer adapts to UI toolkit changes. Adopt modular architecture so entire layers can be replaced without affecting others. Follow open standards (HTTP, JSON, SQL) rather than proprietary solutions where possible. Maintain architecture decision records explaining why boundaries exist, making future refactoring informed. Invest in automated testing so platform migrations can be validated safely. Keep dependencies up-to-date to reduce future upgrade cliffs. Encourage the team to learn platform fundamentals rather than framework-specific magic. Build for change: expect that today's `ViewModel` may be tomorrow's something else.

```kotlin
// Platform-agnostic domain
interface Analytics {
    fun track(event: String, params: Map<String, String>)
}
// Android-specific implementation
class FirebaseAnalyticsImpl @Inject constructor(private val firebase: FirebaseAnalytics) : Analytics {
    override fun track(event: String, params: Map<String, String>) {
        firebase.logEvent(event, bundleOf(*params.toList().toTypedArray()))
    }
}
```

Read more: <https://developer.android.com/topic/architecture>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://notes.tejpratapsingh.com/android-tips/interview/interview-prep-1.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
