Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Core Android Fundamentals


1. Activity Lifecycle

Q: Explain the Activity lifecycle and its callback methods.

The Activity lifecycle represents the states an Activity transitions through from creation to destruction. Proper lifecycle management is essential for resource handling and user experience.

CallbackStateWhat to Do
onCreate()CreatedInitialize UI (setContentView), restore saved state, setup ViewModel
onStart()VisibleStart animations, refresh data that may have changed
onResume()ForegroundRegister sensors/camera, resume video playback
onPause()Partially visiblePause video, save critical data quickly (keep this fast)
onStop()Not visibleRelease heavy resources, unregister receivers, save to database
onDestroy()DestroyedFinal cleanup, cancel non-ViewModel coroutines

Q: What triggers each callback?

  • onStart/onStop: Visibility changes (another Activity covers or uncovers)
  • onResume/onPause: Focus changes (dialog appears, split-screen)
  • onDestroy: User presses back, calls finish(), or system kills for memory

Q: What happens during configuration changes (rotation)?

The Activity is destroyed and recreated: onPause() → onStop() → onDestroy() → onCreate() → onStart() → onResume(). ViewModel survives this process. Use onSaveInstanceState() to preserve UI state (scroll position, form input).


2. Fragment Lifecycle

Q: How does Fragment lifecycle differ from Activity lifecycle?

Fragments have additional callbacks because they’re tied to a host Activity and have a separate view lifecycle:

CallbackPurpose
onAttach()Fragment attached to Activity—get context reference
onCreate()Initialize non-UI components, parse arguments
onCreateView()Inflate and return the fragment’s layout
onViewCreated()Best place to setup UI, observers, click listeners
onDestroyView()View destroyed—must null out binding to prevent leaks
onDetach()Fragment detached from Activity

Q: Why is viewLifecycleOwner important?

The Fragment instance can outlive its view (e.g., on back stack). Using viewLifecycleOwner for LiveData/Flow observation ensures observers are removed when the view is destroyed, preventing memory leaks and crashes from updating dead views.

Q: What’s the critical cleanup in onDestroyView()?

Always set _binding = null. The Fragment survives on the back stack, but holding view references prevents garbage collection of the entire view hierarchy.


3. Configuration Changes

Q: How do you handle configuration changes properly?

Three strategies:

StrategyUse CaseSurvives Process Death?
ViewModelBusiness data, network resultsNo (use SavedStateHandle)
onSaveInstanceStateUI state (scroll position, text input)Yes
configChanges in manifestVideo players, games, camera appsN/A

Q: Why is configChanges in manifest discouraged?

It requires manual handling of all layout adjustments, breaks automatic resource selection (different layouts for orientations), and makes code harder to maintain. Use only for special cases like video playback.

Q: What’s the size limit for onSaveInstanceState?

About 500KB. Use it only for small, essential UI state. For large data, use ViewModel with SavedStateHandle.


4. Android Components

Q: What are the four main Android components?

ComponentPurposeHas UIEntry Point
ActivitySingle screen with UIYesIntent
ServiceBackground operationsNoIntent/Binding
BroadcastReceiverRespond to system/app eventsNoIntent broadcast
ContentProviderShare data between appsNoContent URI

Q: Explain the three types of Services.

  1. Foreground Service: Shows persistent notification, continues when app is backgrounded (music player, fitness tracking). Required for long-running user-visible tasks.

  2. Background Service: No notification, heavily restricted since Android 8.0. Use WorkManager instead for most background work.

  3. Bound Service: Provides client-server interface via onBind(). Lives only while clients are bound to it.

Q: What are Service return values?

  • START_STICKY: Restart with null intent if killed—for ongoing tasks
  • START_NOT_STICKY: Don’t restart—for tasks that can wait
  • START_REDELIVER_INTENT: Restart with last intent—for tasks that must complete

Q: When would you use a ContentProvider?

When sharing structured data with other apps (like Contacts or Calendar). Also useful for accessing data via URIs with CursorLoaders or for integrating with system features like search suggestions.


5. Context

Q: What’s the difference between Application Context and Activity Context?

AspectApplication ContextActivity Context
LifecycleLives with appDies with Activity
UI OperationsCannot show dialogs, inflate themed viewsFull UI support
Memory SafetySafe in singletonsCan cause leaks if held long-term

Q: When to use each?

Application Context:

  • Singletons (database, analytics, image loader)
  • Starting services
  • Registering long-lived receivers
  • Anything that outlives an Activity

Activity Context:

  • Showing dialogs (requires Activity window)
  • Inflating views with correct theme
  • Starting Activities with proper task stack
  • Creating themed UI components

Q: How do memory leaks happen with Context?

Storing Activity Context in a static/singleton object prevents the Activity from being garbage collected. The entire Activity hierarchy stays in memory. Always convert to applicationContext when storing in long-lived objects.


6. Intents

Q: What’s the difference between explicit and implicit Intents?

Explicit Intent: Specifies the exact component to start by class name. Used for internal app navigation.

Implicit Intent: Declares an action and lets the system find matching components. Used for cross-app functionality (share, view URL, send email).

Q: What are Intent flags and when do you use them?

FlagEffect
FLAG_ACTIVITY_NEW_TASKStart in new task (required from non-Activity context)
FLAG_ACTIVITY_CLEAR_TOPPop activities above target, bring target to front
FLAG_ACTIVITY_SINGLE_TOPDon’t create new instance if already at top
FLAG_ACTIVITY_CLEAR_TASKClear entire task before starting

Q: What’s a PendingIntent?

A token that grants another app permission to execute an Intent on your behalf—used for notifications, widgets, and alarms. Specify mutability (MUTABLE/IMMUTABLE) for security on Android 12+.


7. Launch Modes

Q: Explain Activity launch modes.

ModeBehaviorUse Case
standardNew instance every timeMost activities
singleTopReuse if already at top (onNewIntent)Search results, notifications
singleTaskOne instance per task, clears aboveMain/Home screen
singleInstanceAlone in its own taskLauncher, call screen

Q: What’s onNewIntent() and when is it called?

Called instead of creating a new instance when using singleTop (and Activity is at top) or singleTask (and Activity exists in task). Extract new extras here instead of onCreate().


8. Process and App Lifecycle

Q: How does Android prioritize processes for killing?

From least to most likely to be killed:

  1. Foreground (visible Activity, foreground Service)
  2. Visible (partially obscured Activity)
  3. Service (background Service running)
  4. Cached (background Activities, stopped)
  5. Empty (no active components)

Q: How do you handle process death?

  • ViewModel data is lost—use SavedStateHandle for critical state
  • onSaveInstanceState() is called before process death
  • Test with “Don’t keep activities” developer option
  • Activities are recreated with saved Bundle when user returns

Quick Reference

TopicKey Points
Activity LifecycleonCreate (init) → onStart (visible) → onResume (interactive) → reverse for shutdown
Fragment LifecycleAdditional onCreateView/onDestroyView; null binding in onDestroyView
Config ChangesViewModel survives; onSaveInstanceState for UI state; avoid configChanges
ComponentsActivity (UI), Service (background), BroadcastReceiver (events), ContentProvider (data sharing)
ContextApplication for singletons; Activity for UI; never store Activity in long-lived objects
Launch Modesstandard (new), singleTop (reuse at top), singleTask (one per task), singleInstance (own task)

Modern Architecture Patterns


1. MVVM Architecture

Q: Explain MVVM architecture and why we use it in Android.

MVVM (Model-View-ViewModel) separates an application into three layers:

LayerResponsibilityAndroid Components
ModelData and business logicRepository, Data Sources, Room, Retrofit
ViewDisplay UI, capture user inputActivity, Fragment, Composable
ViewModelHold UI state, expose data to ViewJetpack ViewModel

Q: Why is MVVM the recommended pattern?

  1. Separation of Concerns: Each layer has a single responsibility
  2. Testability: ViewModel can be unit tested without Android framework
  3. Lifecycle Awareness: ViewModel survives configuration changes
  4. Maintainability: Changes in one layer don’t cascade to others

Q: What are common MVVM mistakes?

  • Passing Activity/Fragment to ViewModel: Causes memory leaks
  • Holding View references in ViewModel: Same leak issue
  • Business logic in View: Should be in ViewModel or Use Cases
  • ViewModel importing android. packages*: Breaks testability (lifecycle components excepted)

Q: What’s the Single UI State pattern?

Instead of multiple LiveData/StateFlow for different UI elements, use one sealed class representing all possible screen states. This prevents impossible state combinations and makes reasoning about UI easier.


2. MVI Architecture

Q: Explain MVI and when to use it over MVVM.

MVI (Model-View-Intent) enforces unidirectional data flow:

  • Intent: User actions or events (ButtonClicked, TextChanged)
  • Model: Complete, immutable UI state at any moment
  • View: Renders state, emits intents

The flow is circular: User Action → Intent → Reducer → New State → View Update

Q: MVVM vs MVI comparison?

AspectMVVMMVI
StateMultiple StateFlowsSingle ViewState
ComplexityLowerHigher boilerplate
PredictabilityGoodExcellent (single source of truth)
DebuggingModerateEasy (log all state changes)
Best ForSimple-medium appsComplex interactions, forms

Q: When to use MVI?

  • Complex multi-step workflows
  • Forms with interdependent validation
  • Need to track/debug all state changes
  • Time-travel debugging requirements
  • State consistency is critical (financial, healthcare apps)

Q: Describe the layered architecture Google recommends.

Three layers with clear dependencies (outer layers depend on inner):

UI Layer (Presentation)

  • UI elements: Activities, Fragments, Composables
  • State holders: ViewModels
  • Observes state, forwards events

Domain Layer (Optional)

  • Use Cases / Interactors
  • Contains reusable business logic
  • Combines data from multiple repositories

Data Layer

  • Repositories: Single source of truth for each data type
  • Data Sources: Remote (API), Local (Room), Cache

Q: What are the key principles?

  1. Separation of Concerns: UI shouldn’t know about data sources
  2. Drive UI from Data Models: UI reflects state, doesn’t own it
  3. Single Source of Truth (SSOT): One owner per data type
  4. Unidirectional Data Flow: State flows down, events flow up

Q: When is the Domain layer needed?

  • Business logic reused across multiple ViewModels
  • Complex data transformations or combinations
  • Need to enforce business rules consistently
  • Large teams where clear boundaries help

4. Repository Pattern

Q: What is the Repository pattern and why use it?

Repository is an abstraction over data sources. It:

  • Decides whether to fetch from network or cache
  • Handles caching strategies
  • Transforms DTOs to domain models
  • Provides a clean API to the rest of the app

Q: What caching strategies are common?

StrategyBehaviorUse Case
Cache FirstReturn cache, then refreshNews feeds, social content
Network FirstTry network, fallback to cacheCritical data, payments
Cache OnlyNever hit networkOffline-first apps
Network OnlyNever cacheSensitive data

Q: How do you expose data from Repository?

Use Flow for observable data streams. The Room DAO returns Flow, Repository exposes it, ViewModel collects it. Changes propagate automatically from database to UI.


5. Clean Architecture

Q: How does Clean Architecture relate to Android?

Clean Architecture organizes code in concentric circles:

  • Entities (innermost): Business objects, pure Kotlin
  • Use Cases: Application-specific business rules
  • Interface Adapters: ViewModels, Repositories, Presenters
  • Frameworks (outermost): Android, Room, Retrofit

The Dependency Rule: Dependencies point inward. Inner layers know nothing about outer layers.

Q: What are the benefits?

  • Business logic is completely isolated from frameworks
  • Easy to test core logic without Android dependencies
  • Can swap frameworks (e.g., Retrofit → Ktor) without touching business logic
  • Forces clear separation and single responsibility

Q: What are the drawbacks?

  • More boilerplate (mappers, interfaces, multiple models)
  • Can be overkill for simple apps
  • Learning curve for the team
  • Need discipline to maintain boundaries

6. State Management

Q: LiveData vs StateFlow vs SharedFlow?

TypeHot/ColdLifecycle AwareInitial ValueReplay
LiveDataHotYes (automatic)OptionalLatest
StateFlowHotNo (need repeatOnLifecycle)RequiredLatest
SharedFlowHotNoNot requiredConfigurable

Q: When to use each?

  • LiveData: Simple cases, team familiar with it, automatic lifecycle handling
  • StateFlow: UI state, need .value access, Compose integration
  • SharedFlow: One-time events (navigation, toasts), multiple subscribers

Q: How do you handle one-time events in MVVM?

Options:

  1. Channel + receiveAsFlow(): Consumed exactly once
  2. SharedFlow with replay=0: Events don’t replay to new subscribers
  3. Event wrapper class: Wrap value, mark as consumed

Avoid using StateFlow for events—new subscribers receive the last event again.


7. Dependency Injection in Architecture

Q: How does DI fit into the architecture?

DI provides dependencies to each layer:

  • ViewModel receives Repository and Use Cases
  • Repository receives DataSources
  • DataSources receive Retrofit, Room, etc.

This enables:

  • Swapping implementations (real vs fake for testing)
  • Scoping (singleton, per-screen, per-ViewModel)
  • Lazy initialization

Q: What scopes are common in Android DI?

ScopeLifecycleExample
SingletonApp lifetimeRetrofit, OkHttp, Room Database
Activity/FragmentScreen lifetimeScreen-specific analytics
ViewModelViewModel lifetimeUse Cases with state

Quick Reference

PatternKey Insight
MVVMViewModel holds UI state, survives config changes, never references View
MVISingle immutable state, unidirectional flow, great for complex UIs
Layered ArchitectureUI → Domain (optional) → Data; dependencies point inward
RepositoryAbstracts data sources, handles caching, single source of truth
Clean ArchitectureBusiness logic independent of frameworks; more structure, more boilerplate
State ManagementStateFlow for state, Channel/SharedFlow for events, avoid LiveData for new code

Jetpack Compose


1. Recomposition

Q: What is recomposition in Jetpack Compose?

Recomposition is when Compose re-executes composable functions to update the UI after state changes. Compose intelligently recomposes only the composables that read the changed state, not the entire tree.

Q: What triggers recomposition?

Any state read by a composable changing its value. This includes mutableStateOf, StateFlow.collectAsState(), or any State<T> object that the composable reads during execution.

Q: What are the key rules composables must follow?

  1. Idempotent: Same inputs produce same UI
  2. Side-effect free: No external mutations in the function body
  3. Independent of order: Don’t assume execution order
  4. Can be skipped: Compose may skip recomposition if inputs haven’t changed

Q: How does Compose decide what to skip?

Compose compares parameters using equals(). If all parameters are equal to the previous composition, it skips recomposition. For this to work, parameters must be stable—either primitives, immutable data classes, or annotated with @Stable/@Immutable.


2. Stability and Performance

Q: What makes a type “stable” in Compose?

A stable type guarantees that:

  1. equals() always returns the same result for the same instances
  2. If a public property changes, Compose is notified
  3. All public properties are also stable

Primitives, String, and immutable data classes with stable properties are stable by default.

Q: What makes a type “unstable”?

  • Mutable collections (MutableList, ArrayList)
  • Data classes with mutable properties
  • Classes from external libraries Compose can’t verify
  • Lambdas captured in composition

Q: How do you fix stability issues?

ProblemSolution
Mutable collectionUse immutable List, Set, Map
External library classWrap in stable wrapper or use @Immutable
Lambda recreationHoist lambda or use remember
Unknown classAnnotate with @Stable if you guarantee stability

Q: How do you debug recomposition issues?

  • Use Layout Inspector’s “Show Recomposition Counts”
  • Add SideEffect { log("Recomposed") } temporarily
  • Check stability with Compose Compiler reports
  • Profile with Android Studio Profiler

3. State Management

Q: Explain the different ways to hold state in Compose.

APISurvives RecompositionSurvives Config ChangeSurvives Process Death
remember
rememberSaveable
ViewModel❌ (use SavedStateHandle)

Q: What is state hoisting?

Moving state up from a composable to its caller, making the composable stateless. The composable receives the state value and a callback to request changes. Benefits:

  • Reusable composables
  • Easier testing
  • Single source of truth
  • Parent controls the state

Q: When should state live in ViewModel vs Composable?

ViewModelComposable (remember)
Business logic statePure UI state
Data from repositoryAnimation state
Survives config changeScroll position, expansion state
Shared across composablesLocal to one composable

4. Side Effects

Q: What are side effects and why do they need special handling?

Side effects are operations that escape the composable’s scope—network calls, database writes, navigation, analytics. Composables can recompose at any time, in any order, so side effects in the body would execute unpredictably.

Q: Explain the side effect APIs.

APIPurposeLifecycle
LaunchedEffect(key)Launch coroutineCancelled when key changes or leaves composition
DisposableEffect(key)Setup/cleanup resourcesCleanup called when key changes or leaves composition
SideEffectNon-suspending effect on every successful recompositionRuns after every recomposition
rememberCoroutineScope()Get scope for event handlersTied to composition lifecycle
derivedStateOfCompute value from other states, avoiding recompositionUpdates only when result changes
produceStateConvert non-Compose state to Compose stateRuns coroutine to produce values

Q: LaunchedEffect vs rememberCoroutineScope?

  • LaunchedEffect: For effects that should run when entering composition or when a key changes. Automatic lifecycle management.
  • rememberCoroutineScope: For effects triggered by user events (button clicks). You control when to launch.

Q: When do you use DisposableEffect?

For resources that need explicit cleanup:

  • Registering/unregistering listeners
  • Binding to lifecycle
  • Managing third-party SDK lifecycles
  • Callback registrations

5. Composition Local

Q: What is CompositionLocal?

A way to pass data implicitly through the composition tree without explicit parameters. Useful for cross-cutting concerns like theme, navigation, or locale.

Q: compositionLocalOf vs staticCompositionLocalOf?

TypeRecompositionUse Case
compositionLocalOfOnly readers recompose when value changesFrequently changing values
staticCompositionLocalOfEntire subtree recomposes when value changesRarely changing values (theme, locale)

Q: When should you use CompositionLocal?

Sparingly. Good uses:

  • Theme/colors (already provided by MaterialTheme)
  • Navigation controller
  • Dependency injection in Compose

Avoid for business data—use parameters for explicit, traceable data flow.


6. Lists and Performance

Q: How do you build efficient lists in Compose?

Use LazyColumn/LazyRow instead of Column/Row with forEach. Lazy composables only compose visible items.

Q: What is the key parameter and why is it important?

Keys help Compose identify which items changed, moved, or stayed the same. Without keys, Compose uses position—moving an item causes unnecessary recomposition of all items between old and new positions. With stable keys (like IDs), Compose preserves state and animates correctly.

Q: How do you optimize LazyColumn performance?

  1. Always provide keys: Use unique, stable identifiers
  2. Avoid heavy computation in items: Use remember or move to ViewModel
  3. Use contentType: Helps Compose reuse compositions
  4. Avoid nesting scrollable containers: Causes measurement issues
  5. Use derivedStateOf for scroll-dependent state: Reduces recomposition

7. Navigation

Q: How does Navigation work in Compose?

Navigation Compose uses a NavHost composable with a NavController. Routes are strings (optionally with arguments). Each route maps to a composable destination.

Q: How do you pass arguments between screens?

  • Define route with placeholders: "user/{userId}"
  • Navigate with values: navController.navigate("user/123")
  • Receive in destination via NavBackStackEntry.arguments

For complex objects, pass IDs and fetch data in the destination’s ViewModel.

Q: How do you handle navigation events from ViewModel?

Expose a one-time event (Channel or SharedFlow) that the UI collects and acts upon. Don’t hold NavController in ViewModel—it’s a UI concern.


8. Interop with Views

Q: How do you use Views inside Compose?

Use AndroidView composable. Provide a factory to create the View and an update lambda for when state changes. The View is created once and updated on recomposition.

Q: How do you use Compose inside Views?

Use ComposeView in XML or create it programmatically. Call setContent {} to provide composables. In Fragments, set viewCompositionStrategy to match the Fragment’s lifecycle.

Q: What’s important when mixing Compose and Views?

  • Theme bridging: Use MdcTheme or custom bridging
  • Lifecycle: Ensure Compose respects the View’s lifecycle owner
  • State: Be careful about state ownership—one source of truth
  • Performance: Minimize interop boundaries for best performance

Quick Reference

TopicKey Points
RecompositionTriggers on state change; skips stable unchanged inputs; keep composables pure
StabilityImmutable = stable; mutable collections = unstable; use @Stable/@Immutable when needed
Stateremember (composition), rememberSaveable (config change), ViewModel (business logic)
Side EffectsLaunchedEffect for coroutines, DisposableEffect for cleanup, SideEffect for every recomposition
ListsUse LazyColumn; always provide keys; use contentType for mixed lists
NavigationNavHost + NavController; string routes; pass IDs not objects

Kotlin Advanced Features


1. Coroutines & Structured Concurrency

Q: What are Kotlin Coroutines and why are they important for Android?

Coroutines are lightweight, suspendable computations that allow you to write asynchronous code in a sequential, readable style. Unlike threads, coroutines are extremely cheap to create—you can launch thousands without significant overhead.

The key insight is that coroutines suspend rather than block. When a coroutine hits a suspension point (like a network call), it releases the thread to do other work, then resumes when the result is ready.

Q: Explain structured concurrency.

Structured concurrency means coroutines follow a parent-child hierarchy. When you launch a coroutine within a scope:

  • The parent waits for all children to complete
  • If the parent is cancelled, all children are cancelled
  • If a child fails, the parent (and siblings) are cancelled by default

In Android, viewModelScope and lifecycleScope provide built-in structured concurrency—coroutines are automatically cancelled when the ViewModel clears or the lifecycle ends, preventing memory leaks and wasted work.

Q: What are the different Dispatchers and when do you use each?

DispatcherThread PoolUse Case
MainAndroid main threadUI updates, light work
IOShared pool (64+ threads)Network, database, file I/O
DefaultCPU coresHeavy computation, sorting, parsing
UnconfinedInherits caller’s threadTesting only, avoid in production

Q: How do Dispatchers work under the hood?

Dispatchers are CoroutineContext elements that determine which thread(s) execute the coroutine. When you call withContext(Dispatchers.IO), the coroutine suspends, moves to an IO thread, executes the block, then resumes on the original dispatcher.

  • Main uses Android’s main Looper—there’s only one main thread
  • IO and Default share threads but have different policies. IO allows many concurrent operations (blocking calls), while Default is sized to CPU cores (compute-bound work)
  • IO can grow beyond 64 threads if needed, but reuses threads from Default when possible

Q: What is withContext vs switching dispatchers with launch?

withContext suspends the current coroutine and runs the block on another dispatcher, then returns the result. It’s for sequential switching within a coroutine.

launch(Dispatchers.IO) creates a new child coroutine on that dispatcher. Use this for parallel/concurrent work.

Q: What’s the difference between launch and async?

  • launch: Fire-and-forget. Returns a Job. Use when you don’t need a result.
  • async: Returns a Deferred<T>. Use when you need to compute a value, especially for parallel operations where you await() multiple results.

Q: How does exception handling work in coroutines?

Regular launch propagates exceptions to the parent, which cancels siblings. Use SupervisorJob when you want child failures to be independent—one child’s exception won’t cancel others.

For async, exceptions are thrown when you call await(), so wrap that in try-catch.


2. Flow & Reactive Streams

Q: What is Kotlin Flow?

Flow is a cold asynchronous stream that emits multiple values over time. “Cold” means it doesn’t produce values until someone collects it, and each collector gets its own independent stream.

Q: Compare Flow, StateFlow, and SharedFlow.

TypeHot/ColdInitial ValueReplayUse Case
FlowColdNoNoOne-shot data, transformations
StateFlowHotRequiredLatest valueUI state, always has current value
SharedFlowHotOptionalConfigurableEvents, can have multiple subscribers

Q: What are the key Flow operators?

  • map/filter: Transform or filter emissions
  • flatMapLatest: Cancel previous work when new value arrives (great for search)
  • combine: Merge multiple flows, emit when any changes
  • debounce: Wait for pause in emissions (search input)
  • catch: Handle upstream errors
  • flowOn: Change dispatcher for upstream operations
  • stateIn/shareIn: Convert cold Flow to hot StateFlow/SharedFlow

Q: How does backpressure work in Flow?

Backpressure occurs when the producer emits faster than the consumer can process. Flow handles this naturally because it’s sequential by default—the producer suspends until the collector processes each emission.

For cases where you need different behavior:

  • buffer(): Runs collector in separate coroutine, emissions don’t wait for processing
  • conflate(): Skip intermediate values, only process the latest when collector is ready
  • collectLatest(): Cancel previous collection when new value arrives (similar to flatMapLatest)

Use buffer() when you want to decouple producer/consumer speeds. Use conflate() or collectLatest() when only the latest value matters (UI updates, search queries).

Q: How do you safely collect Flow in Android?

Use repeatOnLifecycle or flowWithLifecycle to collect only when the UI is visible. This prevents wasted work when the app is in the background and avoids crashes from updating views after the Activity is destroyed.


3. Generics & Variance

Q: Explain variance in Kotlin generics.

Variance defines how generic types with inheritance relate to each other.

Invariant (default): Box<Dog> is NOT a subtype of Box<Animal>, even though Dog extends Animal. This is safe because the box could accept writes.

Covariant (out): The type can only be produced/returned, never consumed. List<out T> means you can read items but not add them. A List<Dog> IS a subtype of List<Animal>.

Contravariant (in): The type can only be consumed, never produced. Comparable<in T> means you can pass items in but not get them out. A Comparator<Animal> IS a subtype of Comparator<Dog>.

Q: How do you remember in vs out?

  • out = output position only (return types) → Producer
  • in = input position only (parameter types) → Consumer

Think “PECS” from Java: Producer Extends, Consumer Super.


4. Sealed Classes vs Enums

Q: When should you use sealed classes instead of enums?

Enums are perfect for fixed sets of constants with no varying data—like days of the week or simple status codes.

Sealed classes are better when:

  • Different states carry different data (Success has data, Error has message)
  • You need inheritance hierarchies
  • Subtypes are data classes or objects with properties

The compiler enforces exhaustive when expressions for both, but sealed classes let each subtype have its own structure.

Q: What about sealed interfaces?

Sealed interfaces (Kotlin 1.5+) allow a class to implement multiple sealed hierarchies, which sealed classes can’t do. Use them when you need more flexible type modeling.


5. Inline Functions & Reified Types

Q: What does inline do?

The compiler copies the function body directly to every call site, eliminating the function call overhead and lambda object allocation. This matters for higher-order functions called frequently.

Q: What is reified and why is it useful?

Normally, generic type parameters are erased at runtime—you can’t access T::class. The reified keyword (only works with inline functions) preserves the type information, letting you:

  • Get the class: T::class.java
  • Check types: value is T
  • Create instances or pass to reflection APIs

Common uses: JSON parsing (gson.fromJson<User>(json)), ViewModel creation, intent extras.

Q: What are the downsides of inline?

  • Increases bytecode size if overused
  • Can’t be used with recursive functions
  • Public inline functions can’t access private members

6. Extension Functions

Q: How do extension functions work internally?

They’re compiled as static methods where the receiver becomes the first parameter. fun String.isEmail() becomes static boolean isEmail(String $this). They’re resolved statically at compile time, not dynamically.

Q: What can’t extension functions do?

  • Access private/protected members of the class
  • Override existing member functions (members always win)
  • Be truly polymorphic (static dispatch, not virtual)

Q: Explain the scope functions (let, apply, run, also, with).

FunctionReturnsReceiver AccessUse Case
letLambda resultitNull checks, transformations
applyReceiver objectthisObject configuration
runLambda resultthisExecute block, compute result
alsoReceiver objectitSide effects, logging
withLambda resultthisMultiple calls on same object

7. Data Classes

Q: What does declaring a data class give you automatically?

  • equals() and hashCode() based on constructor properties
  • toString() with property names and values
  • copy() for creating modified copies
  • componentN() functions for destructuring

Q: What are the limitations?

  • Must have at least one primary constructor parameter
  • All constructor parameters must be val or var
  • Cannot be abstract, open, sealed, or inner
  • Only primary constructor properties are used in generated methods

Q: Best practices for data classes?

  • Prefer val for immutability
  • Avoid mutable collections as properties
  • Be careful with properties declared in the body—they’re excluded from equals/hashCode
  • Consider using copy() instead of mutable properties

Quick Reference

TopicKey Points
CoroutinesSuspend don’t block; structured concurrency with scopes; Main/IO/Default dispatchers
FlowCold streams; StateFlow for state; SharedFlow for events; flatMapLatest for search
Varianceout = producer (covariant); in = consumer (contravariant)
Sealed ClassesStates with data; exhaustive when; better than enum for complex cases
Inline/ReifiedInline copies body; reified preserves generic type at runtime
ExtensionsStatic dispatch; can’t access private; scope functions for fluent code
Data ClassesAuto equals/hashCode/copy/toString; immutable preferred

Dependency Injection


1. DI Fundamentals

Q: What is Dependency Injection and why use it?

Dependency Injection is a design pattern where objects receive their dependencies from external sources rather than creating them internally. Benefits:

  • Testability: Swap real implementations with fakes/mocks
  • Decoupling: Classes don’t know how dependencies are created
  • Reusability: Same class works with different implementations
  • Maintainability: Change dependency configuration in one place

Q: What’s the difference between DI and Service Locator?

Dependency InjectionService Locator
Dependencies pushed to objectObject pulls dependencies
Dependencies explicit in constructorDependencies hidden inside class
Compile-time verificationRuntime failures possible
Easier to testRequires global state management

Q: Constructor injection vs field injection?

Constructor injection (preferred): Dependencies are required parameters. Object can’t exist without them. Immutable, testable, clear dependencies.

Field injection: Dependencies set after construction via annotation. Required for Android components (Activity, Fragment) where we don’t control construction. Use sparingly elsewhere.


2. Hilt

Q: What is Hilt and how does it relate to Dagger?

Hilt is Google’s DI library built on top of Dagger. It provides:

  • Predefined components matching Android lifecycles
  • Standard scopes (Singleton, ActivityScoped, etc.)
  • Built-in support for ViewModel injection
  • Less boilerplate than pure Dagger

Dagger is the underlying code generation engine; Hilt adds Android-specific conventions.

Q: What are the key Hilt annotations?

AnnotationPurpose
@HiltAndroidAppTrigger Hilt code generation on Application class
@AndroidEntryPointEnable injection in Activity, Fragment, Service, etc.
@HiltViewModelEnable constructor injection for ViewModel
@InjectMark constructor for injection or field for field injection
@ModuleClass that provides dependencies
@InstallInSpecify which component the module belongs to
@ProvidesMethod that creates a dependency
@BindsBind interface to implementation (more efficient than @Provides)

Q: Explain Hilt’s component hierarchy.

SingletonComponent (Application lifetime)
    ↓
ActivityRetainedComponent (survives config changes)
    ↓
ViewModelComponent (ViewModel lifetime)
    ↓
ActivityComponent → FragmentComponent → ViewComponent

Objects in parent components can be injected into child components, but not vice versa.


3. Scopes

Q: What are scopes and why do they matter?

Scopes control the lifetime and sharing of dependencies. Without scoping, Hilt creates a new instance every time one is requested.

ScopeLifetimeUse Case
@SingletonApp lifetimeRetrofit, OkHttp, Database
@ActivityRetainedScopedSurvives rotationShared state across Activity recreations
@ViewModelScopedViewModel lifetimeDependencies specific to one ViewModel
@ActivityScopedActivity lifetimeActivity-specific presenters
@FragmentScopedFragment lifetimeFragment-specific logic

Q: What’s the cost of @Singleton?

Singleton objects live for the entire app lifetime, consuming memory even when not needed. They can’t be garbage collected. Use only for truly app-wide dependencies.

Q: How do scopes affect testing?

Scoped dependencies are shared within that scope. In tests, you may need to replace the entire scoped object, not just one usage. Hilt provides @TestInstallIn to swap modules for tests.


4. Providing Dependencies

Q: When do you use @Binds vs @Provides?

@Binds@Provides
Interface → Implementation mappingComplex object creation
Abstract methodConcrete method
No method bodyMethod body with creation logic
More efficient (no extra method call)Required for third-party classes

Q: How do you provide third-party dependencies?

Use @Provides in a module since you can’t add @Inject to classes you don’t own:

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    @Provides
    @Singleton
    fun provideRetrofit(): Retrofit = Retrofit.Builder()
        .baseUrl("https://api.example.com/")
        .build()
}

Q: What are Qualifiers and when do you need them?

Qualifiers distinguish between multiple bindings of the same type. Example: You might have two OkHttpClient instances—one with auth interceptor, one without. Use custom qualifier annotations to differentiate.


5. ViewModel Injection

Q: How does ViewModel injection work in Hilt?

Mark ViewModel with @HiltViewModel and use @Inject constructor. Hilt creates a ViewModelProvider.Factory automatically. In UI, use by viewModels() (Fragment) or hiltViewModel() (Compose).

Q: How do you pass arguments to ViewModel via Hilt?

Use SavedStateHandle—it’s automatically populated with arguments from the Fragment’s bundle or Navigation arguments.

@HiltViewModel
class DetailViewModel @Inject constructor(
    private val repository: Repository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    private val itemId: String = savedStateHandle.get<String>("itemId")!!
}

Q: How do you inject assisted dependencies (runtime values)?

Use @AssistedInject and @AssistedFactory. The factory takes runtime parameters that can’t be provided by Hilt.


6. Testing with Hilt

Q: How do you test with Hilt?

  1. Use @HiltAndroidTest on test class
  2. Add HiltAndroidRule as a JUnit rule
  3. Call hiltRule.inject() before tests
  4. Use @TestInstallIn to replace production modules

Q: How do you replace dependencies for testing?

Create a test module that replaces the production module:

@Module
@TestInstallIn(
    components = [SingletonComponent::class],
    replaces = [RepositoryModule::class]
)
abstract class FakeRepositoryModule {
    @Binds
    abstract fun bind(impl: FakeRepository): Repository
}

Q: What about unit testing ViewModels?

For unit tests, skip Hilt entirely. Construct ViewModel directly with fake dependencies:

@Test
fun `test viewmodel`() {
    val fakeRepository = FakeRepository()
    val viewModel = MyViewModel(fakeRepository)
    // test...
}

7. Common Patterns

Q: How do you handle optional dependencies?

Use @Nullable or provide a default. Hilt doesn’t support optional bindings directly—every requested dependency must have a binding.

Q: How do you handle multibindings?

Hilt supports @IntoSet and @IntoMap for collecting multiple implementations:

@Provides
@IntoSet
fun provideInterceptor(): Interceptor = LoggingInterceptor()

All items annotated with @IntoSet for that type are collected into a Set<Interceptor>.

Q: How do you lazily initialize dependencies?

Inject Lazy<T> or Provider<T>:

  • Lazy<T>: Creates instance on first .get(), returns same instance thereafter
  • Provider<T>: Creates new instance on every .get() call

Quick Reference

ConceptKey Points
HiltDagger + Android conventions; @HiltAndroidApp, @AndroidEntryPoint, @HiltViewModel
Scopes@Singleton (app), @ViewModelScoped (VM), @ActivityScoped (activity); unscoped = new instance each time
@BindsInterface → impl mapping; abstract, more efficient
@ProvidesComplex creation, third-party classes; concrete method body
QualifiersDistinguish same-type bindings; custom annotations
Testing@HiltAndroidTest, @TestInstallIn to swap modules; unit tests skip Hilt

Networking and Data


1. Networking Fundamentals

Q: Explain the OSI model layers relevant to mobile development.

LayerProtocolMobile Relevance
Application (7)HTTP, WebSocket, gRPCAPI communication
Transport (4)TCP, UDPReliability vs speed
Network (3)IPRouting, addressing
Link (2)WiFi, CellularConnection type affects behavior

Most Android work happens at Application layer; understanding Transport helps with optimization.

Q: TCP vs UDP—when would you use each?

TCPUDP
Connection-orientedConnectionless
Guaranteed delivery, orderingNo guarantees
Slower, more overheadFast, lightweight
HTTP, HTTPS, WebSocketVideo streaming, VoIP, gaming

Mobile apps typically use TCP (via HTTP) for reliability. UDP for real-time where dropped packets are acceptable.

Q: Explain HTTP/1.1 vs HTTP/2 vs HTTP/3.

VersionConnectionKey Features
HTTP/1.1One request per connection (or keep-alive)Sequential, head-of-line blocking
HTTP/2Multiplexed streams over one connectionParallel requests, header compression, server push
HTTP/3QUIC (UDP-based)Faster handshakes, no TCP head-of-line blocking

OkHttp supports HTTP/2 by default. HTTP/3 adoption is growing.

Q: What is TLS/SSL and why is it important?

TLS (Transport Layer Security) encrypts communication:

  • Confidentiality: Data can’t be read in transit
  • Integrity: Data can’t be modified
  • Authentication: Server proves identity via certificate

Android enforces HTTPS by default (cleartext blocked). TLS 1.2+ required for modern apps.

Q: What happens during a TLS handshake?

  1. Client sends supported cipher suites
  2. Server chooses cipher, sends certificate
  3. Client verifies certificate against trusted CAs
  4. Key exchange (asymmetric) establishes session key
  5. Symmetric encryption begins

This adds latency; HTTP/2 and HTTP/3 reduce repeat handshakes.


2. REST & API Design

Q: What is REST and its principles?

REST (Representational State Transfer) is an architectural style:

PrincipleMeaning
StatelessEach request contains all needed info
Client-ServerSeparation of concerns
CacheableResponses indicate cacheability
Uniform InterfaceStandard methods (GET, POST, PUT, DELETE)
LayeredClient doesn’t know if talking to server or intermediary

Q: Explain HTTP methods and when to use each.

MethodPurposeIdempotentSafe
GETRetrieve resourceYesYes
POSTCreate resourceNoNo
PUTReplace resourceYesNo
PATCHPartial updateNoNo
DELETERemove resourceYesNo

Idempotent: Same request multiple times = same result Safe: Doesn’t modify server state

Q: What are common HTTP status codes?

CodeMeaningAction
200OKSuccess
201CreatedResource created (POST)
204No ContentSuccess, no body
400Bad RequestClient error, fix request
401UnauthorizedNeed authentication
403ForbiddenAuthenticated but not allowed
404Not FoundResource doesn’t exist
429Too Many RequestsRate limited, backoff
500Server ErrorServer problem, maybe retry
503Service UnavailableTemporary, retry with backoff

Q: REST vs GraphQL vs gRPC?

AspectRESTGraphQLgRPC
Data FormatJSONJSONProtobuf (binary)
FlexibilityFixed endpointsClient specifies fieldsDefined services
Over-fetchingCommonSolvedN/A
PerformanceGoodGoodBest (binary)
Mobile UseMost commonGrowingBackend-to-backend, some mobile

3. WebSocket & Real-Time

Q: When would you use WebSocket instead of HTTP?

HTTPWebSocket
Request-responseBidirectional, persistent
Client initiatesEither side can send
StatelessStateful connection
Polling for updatesPush updates

Use WebSocket for: chat, live updates, collaborative editing, gaming.

Q: How does WebSocket work?

  1. HTTP request with Upgrade: websocket header
  2. Server responds 101 Switching Protocols
  3. Connection upgrades to WebSocket
  4. Full-duplex communication over same TCP connection
  5. Either side can close

Q: What alternatives exist for real-time updates?

TechniqueHow It WorksTrade-off
PollingRepeated HTTP requestsSimple but wasteful
Long PollingServer holds request until dataBetter but complex
SSEServer-Sent Events, one-way streamSimple, HTTP-based
WebSocketFull duplexMost capable, more complex
Push NotificationsFCM/APNsWorks when app closed

4. Retrofit & OkHttp

Q: Explain the Android networking stack.

The typical stack consists of:

  • Retrofit: Type-safe HTTP client, converts API endpoints to Kotlin interfaces
  • OkHttp: Underlying HTTP client handling connections, caching, interceptors
  • Gson/Moshi/Kotlinx.serialization: JSON parsing

Retrofit uses OkHttp internally. You configure OkHttp with interceptors and timeouts, then pass it to Retrofit.

Q: What are interceptors and what types exist?

Interceptors observe, modify, and potentially short-circuit requests/responses.

TypeRuns WhenUse Case
Application InterceptorBefore OkHttp coreAdd headers, logging, modify requests
Network InterceptorAfter connection establishedAccess network-level info, redirects

Common interceptors:

  • Authentication: Add auth headers to every request
  • Logging: Log request/response for debugging
  • Retry: Retry failed requests with backoff
  • Caching: Custom cache control

Q: How do you handle authentication token refresh?

Use an Authenticator (not interceptor). When a 401 is received, the authenticator refreshes the token and retries the request. This handles the race condition of multiple simultaneous requests hitting 401.

Q: How do you handle errors?

Map HTTP errors to domain exceptions:

  • IOException: Network failures (no connection, timeout)
  • HttpException: Server returned error status (401, 404, 500)

Create sealed classes like Result<T> or NetworkResult<T> to represent Success/Error states uniformly.


5. Room Database

Q: What is Room and its components?

Room is Jetpack’s SQLite abstraction providing:

  • Entity: Data class representing a table (@Entity)
  • DAO: Interface with SQL queries (@Dao, @Query, @Insert)
  • Database: Abstract class holding DAOs (@Database)

Room generates implementation at compile time, catches SQL errors early.

Q: How does Room integrate with reactive streams?

DAOs can return Flow<T> or LiveData<T> for observable queries. When data changes, observers automatically receive updates.

Q: What are common Room annotations?

AnnotationPurpose
@EntityDefine table
@PrimaryKeyPrimary key column
@ColumnInfoCustomize column name
@EmbeddedFlatten nested object into table
@RelationDefine relationship between entities
@TypeConverterConvert custom types to/from SQLite types

Q: How do you handle database migrations?

Define Migration objects specifying how to alter schema from version N to N+1. Room runs migrations sequentially. If migration is missing, Room throws an exception (fail-safe). Use fallbackToDestructiveMigration() only for development.

Q: What about testing Room?

Use in-memory database for tests:

Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java)
    .allowMainThreadQueries() // Only for tests!
    .build()

6. DataStore

Q: What is DataStore and how does it differ from SharedPreferences?

DataStore is the modern replacement for SharedPreferences:

SharedPreferencesDataStore
Synchronous APIAsync (Flow-based)
Can block main threadNever blocks
No type safetyType-safe with Proto DataStore
Exception handling unclearExceptions in Flow
No transaction guaranteesTransactional

Q: What are the two types of DataStore?

  1. Preferences DataStore: Key-value pairs, like SharedPreferences but async
  2. Proto DataStore: Typed objects using Protocol Buffers, schema-defined

Q: When do you use each?

  • Preferences DataStore: Simple settings, feature flags, user preferences
  • Proto DataStore: Complex structured data, type safety critical
  • Room: Relational data, complex queries, multiple tables

7. Caching Strategies

Q: What caching strategies are common in Android?

StrategyFlowUse Case
Cache-firstReturn cache → fetch network → update cacheNews feeds, social content
Network-firstTry network → fallback to cacheCritical data, stock prices
Cache-onlyOnly return cacheOffline-first apps
Network-onlyAlways fetch, no cacheAuthentication, payments
Stale-while-revalidateReturn stale cache → update in backgroundBest UX for most apps

Q: How do you implement cache-first with Room + Retrofit?

  1. Repository observes Room via Flow
  2. On request, emit cached data immediately
  3. Fetch from network in parallel
  4. Insert network data into Room
  5. Room Flow automatically emits updated data

This gives instant UI with fresh data arriving shortly after.

Q: How do you determine cache validity?

  • Timestamp-based: Store fetch time, expire after duration
  • ETags: Let server determine if data changed
  • Server-provided expiry: Use Cache-Control headers
  • Event-based: Invalidate on specific user actions

8. Pagination

Q: How does Paging 3 work?

Paging 3 loads data in chunks as the user scrolls:

ComponentRole
PagingSourceLoads pages from single source (network or database)
RemoteMediatorLoads network data into database for offline support
PagerProduces PagingData Flow
PagingDataAdapterSubmits PagingData to RecyclerView/LazyColumn

Q: PagingSource vs RemoteMediator?

  • PagingSource alone: Simple pagination, network-only or database-only
  • RemoteMediator + PagingSource: Offline-first. RemoteMediator fetches network data into DB, PagingSource loads from DB. Best practice for production apps.

Q: How do you handle errors and retry in Paging?

LoadState contains Loading, NotLoading, and Error states for prepend, append, and refresh. Display loading/error UI based on these states. Provide retry action that calls adapter.retry().


9. Image Loading

Q: What does an image loading library do?

  • Async download with disk/memory caching
  • Image transformations (resize, crop, round)
  • Placeholder and error images
  • Lifecycle awareness (cancel on destroy)
  • Memory management (avoid OOM)

Q: Coil vs Glide?

CoilGlide
Kotlin-first, uses coroutinesJava-based, custom threading
Lighter, modernMore features, mature
Compose integration built-inCompose support via extension
10KB method countLarger footprint

Both are excellent. Coil is preferred for new Kotlin-only projects; Glide for maximum feature set.

Q: How do you optimize image loading?

  • Request correct size (don’t load 4000px for 100dp view)
  • Use appropriate format (WebP for compression)
  • Enable hardware bitmaps where supported
  • Cache appropriately (memory for frequent, disk for all)
  • Preload upcoming images in lists

Quick Reference

TopicKey Points
HTTP BasicsTCP for reliability; HTTP/2 for multiplexing; TLS for security
RESTStateless; GET/POST/PUT/DELETE; status codes communicate result
WebSocketBidirectional; persistent connection; use for chat, live updates
Retrofit/OkHttpType-safe client; interceptors for headers/logging; Authenticator for 401
RoomEntity + DAO + Database; Flow for reactive queries; migrations for schema
DataStoreAsync SharedPreferences replacement; Preferences or Proto types
CachingCache-first for UX; network-first for critical data; Room as cache
PagingPagingSource for simple; RemoteMediator for offline-first; handle LoadState

Concurrency and Threading


1. Android Threading Model

Q: What is Android’s threading rule?

Never block the main (UI) thread. The main thread:

  • Handles all UI rendering and user input
  • Blocks for >5 seconds → ANR (Application Not Responding)
  • Must be responsive for 60fps (16ms per frame)

All long operations (network, database, heavy computation) must run on background threads.

Q: What are the options for background work?

ToolUse CaseLifecycle
CoroutinesMost async workTied to scope (viewModelScope, lifecycleScope)
WorkManagerGuaranteed, deferrable workSurvives process death, respects constraints
Foreground ServiceLong-running user-visible workLives independently of UI
Handler/LooperLegacy, low-level thread communicationManual management

Q: Why are coroutines preferred over RxJava or raw threads?

  • First-party Kotlin support
  • Simpler syntax (sequential code style)
  • Structured concurrency prevents leaks
  • Less boilerplate than RxJava
  • Built-in cancellation
  • Better Compose/Flow integration

2. Coroutine Dispatchers

Q: What are the main dispatchers and their characteristics?

DispatcherThread PoolOptimized For
MainSingle main threadUI updates, light work
IO64+ threads (elastic)Blocking I/O (network, disk)
DefaultCPU core countCPU-intensive computation
UnconfinedInherits callerTesting only (dangerous in production)

Q: How do IO and Default share threads?

They share the same underlying pool but have different limits. IO can scale to many threads (blocking calls don’t hurt), while Default is limited to CPU cores (compute-bound work benefits from locality).

Q: What’s the difference between withContext and launch?

  • withContext: Suspends current coroutine, runs block on specified dispatcher, returns result. For sequential dispatcher switching.
  • launch(Dispatcher): Creates new child coroutine on that dispatcher. For parallel/concurrent work.

3. Structured Concurrency

Q: What is structured concurrency?

Coroutines follow a parent-child hierarchy within a scope:

  • Parent waits for all children to complete
  • Cancelling parent cancels all children
  • Child failure propagates to parent (by default)

This prevents orphan coroutines and ensures cleanup.

Q: What scopes should you use in Android?

ScopeLifecycleUse Case
viewModelScopeViewModelMost business logic
lifecycleScopeActivity/FragmentUI-related work
rememberCoroutineScopeCompositionCompose event handlers
CoroutineScopeCustomServices, application-level

Q: Why avoid GlobalScope?

GlobalScope creates coroutines that:

  • Never auto-cancel (leak potential)
  • Live for app lifetime
  • Can’t be tested properly
  • Violate structured concurrency

Use custom scopes instead, cancelled when appropriate.


4. Exception Handling

Q: How do exceptions propagate in coroutines?

BuilderException Behavior
launchPropagates to parent immediately, cancels siblings
asyncStored until await() is called, then thrown

Q: What is SupervisorJob and when do you use it?

SupervisorJob prevents child failure from canceling siblings. Use when:

  • Multiple independent operations shouldn’t affect each other
  • UI can show partial results even if some fail
  • Retry logic for individual failures

viewModelScope uses SupervisorJob by default.

Q: How should you handle exceptions?

try/catch in coroutine → Handle expected errors (network, parsing)
CoroutineExceptionHandler → Global fallback for uncaught exceptions
SupervisorJob → Isolate failures between siblings

For async, always wrap await() in try/catch or use runCatching.


5. Cancellation

Q: How does coroutine cancellation work?

Cancellation is cooperative. Coroutines must check for cancellation:

  • isActive property
  • Suspending functions (delay, yield, withContext)
  • ensureActive() call

If you have a tight loop without suspension, it won’t cancel until it checks.

Q: What happens on cancellation?

CancellationException is thrown. It’s special:

  • Doesn’t propagate to parent as failure
  • Caught silently by the coroutine machinery
  • finally blocks still execute for cleanup

Q: How do you clean up on cancellation?

Use finally block:

try {
    doWork()
} finally {
    // Always runs, even on cancellation
    cleanup()
}

For suspending cleanup, wrap in withContext(NonCancellable).


6. Thread Safety

Q: What thread safety issues occur with coroutines?

Even with single-threaded dispatcher (Main), coroutines can interleave at suspension points. With multi-threaded dispatchers (IO, Default), classic race conditions apply.

Q: How do you achieve thread safety?

ApproachUse Case
MutexProtect critical sections (like synchronized)
Atomic typesSingle variable updates (AtomicInteger, etc.)
Single-threaded confinementKeep state access on one dispatcher
Immutable dataPrevent mutation entirely
ChannelThread-safe communication between coroutines

Q: What’s the Actor pattern?

A coroutine that processes messages sequentially from a Channel. All state is confined to the actor, eliminating race conditions. Messages are sent from any thread, processed one at a time.


7. WorkManager

Q: When do you use WorkManager?

For guaranteed background work that must complete even if:

  • App is killed
  • Device restarts
  • Process dies

Examples: syncing data, uploading logs, periodic cleanup.

Q: What are WorkManager constraints?

ConstraintPurpose
NetworkTypeRequire connected, unmetered, etc.
BatteryNotLowWait for sufficient battery
StorageNotLowWait for storage space
DeviceIdleWait for device idle
RequiresChargingWait for charging

Q: OneTimeWorkRequest vs PeriodicWorkRequest?

  • OneTimeWorkRequest: Run once, optionally with initial delay
  • PeriodicWorkRequest: Repeat at intervals (min 15 minutes)

Q: How do you chain work?

workManager
    .beginWith(downloadWork)
    .then(processWork)
    .then(uploadWork)
    .enqueue()

Parallel work uses beginWith(listOf(work1, work2)).


Quick Reference

TopicKey Points
Threading RuleNever block main thread; use background threads for I/O and computation
DispatchersMain (UI), IO (blocking I/O), Default (CPU), Unconfined (testing only)
Structured ConcurrencyParent-child hierarchy; automatic cancellation; use viewModelScope/lifecycleScope
Exceptionslaunch propagates immediately; async stores until await; use SupervisorJob for isolation
CancellationCooperative; check isActive or use suspending functions; cleanup in finally
Thread SafetyMutex for critical sections; immutable data; single-threaded confinement
WorkManagerGuaranteed execution; survives process death; respects constraints

Performance Optimization


1. Memory Management

Q: What causes memory leaks in Android?

A memory leak occurs when objects that should be garbage collected are still referenced. Common causes:

CauseExampleFix
Static reference to ContextSingleton holding ActivityUse Application Context
Non-static inner classHandler, AsyncTask referencing ActivityMake static, use WeakReference
Unregistered listenersBroadcast receiver not unregisteredUnregister in onStop/onDestroy
Long-lived referencesViewModel holding View referenceNever store View in ViewModel
Binding not clearedFragment binding outlives viewNull binding in onDestroyView

Q: How do you detect memory leaks?

  • LeakCanary: Automatic detection in debug builds
  • Android Studio Profiler: Memory allocation tracking
  • Heap dump analysis: Find objects that should be GC’d
  • StrictMode: Detect leaked closeable objects

Q: What is the memory hierarchy on Android?

Registers → L1 Cache → L2 Cache → RAM → Disk
(fastest)                              (slowest)

Allocations are cheap, but GC pauses affect UI. Reduce allocations in hot paths (onDraw, adapters).


2. ANR Prevention

Q: What causes ANR (Application Not Responding)?

  • Main thread blocked for >5 seconds for input events
  • BroadcastReceiver not completing within 10 seconds
  • Service not starting within 20 seconds

Q: How do you prevent ANRs?

Don’tDo Instead
Database queries on main threadUse Room with coroutines/Flow
Network calls on main threadUse Retrofit suspend functions
Heavy computation on main threadUse withContext(Dispatchers.Default)
Complex View inflationUse ViewStub for lazy inflation
Synchronous file I/OUse withContext(Dispatchers.IO)

Q: How do you debug ANRs?

  1. Check traces.txt in /data/anr/
  2. Look for main thread stack trace
  3. Find blocking operation
  4. Use StrictMode in development to catch early

3. UI Performance

Q: What causes jank (dropped frames)?

Each frame must complete in 16ms for 60fps. Causes of jank:

  • Overdraw (drawing same pixel multiple times)
  • Complex view hierarchies (nested layouts)
  • Heavy onDraw operations
  • Layout thrashing (measure/layout during animation)
  • Main thread blocking

Q: How do you reduce overdraw?

  • Remove unnecessary backgrounds
  • Use clipRect() and quickReject() in custom views
  • Flatten view hierarchy
  • Use tools:showOverdraw to visualize

Q: How do you optimize view hierarchies?

ProblemSolution
Nested LinearLayoutsUse ConstraintLayout (flat hierarchy)
Views that aren’t always shownUse ViewStub for lazy inflation
Complex item layoutsSimplify, use merge tag
Frequent layout changesUse TransitionManager for batched changes

Q: RecyclerView optimization?

  • Use DiffUtil for efficient list updates
  • Set setHasFixedSize(true) if size doesn’t change
  • Use RecycledViewPool for nested RecyclerViews
  • Avoid inflation in onBindViewHolder
  • Use stable IDs for item animations

4. Rendering Pipeline

Q: Explain Android’s rendering pipeline.

  1. Measure: Calculate view sizes
  2. Layout: Position views in hierarchy
  3. Draw: Render to canvas
  4. Sync & Upload: Transfer to GPU
  5. Issue Draw Commands: GPU renders
  6. Swap Buffers: Display on screen

Double buffering ensures smooth display while next frame prepares.

Q: What is hardware acceleration?

GPU handles rendering instead of CPU. Enabled by default. Benefits:

  • Faster for most operations
  • Frees CPU for other work

Some operations aren’t hardware-accelerated (complex Path operations). Use software layer for those.


5. Startup Performance

Q: What affects app startup time?

PhaseBottlenecks
Cold startProcess creation, class loading, Application init
Warm startActivity creation, but process exists
Hot startActivity resumes, already in memory

Q: How do you optimize cold start?

  • Minimize work in Application.onCreate()
  • Use lazy initialization for non-essential services
  • Defer heavy initialization with App Startup library
  • Use splash screen (Android 12+ SplashScreen API)
  • Reduce class loading (smaller APK, fewer libraries)
  • Enable baseline profiles (ART optimization)

Q: What are Baseline Profiles?

AOT-compiled methods for common user journeys. Reduces JIT compilation on first run. Create using Macrobenchmark, include in release APK.


6. Battery Optimization

Q: What drains battery on Android?

ComponentImpact
CPU wakeupsHigh—keeping CPU active
NetworkHigh—radio power state
GPSVery high—continuous location
SensorsMedium—depends on sensor
ScreenHighest—display on

Q: How do you reduce battery usage?

  • Batch network requests (don’t wake radio repeatedly)
  • Use WorkManager with constraints (wait for charging)
  • Request coarse location when fine isn’t needed
  • Use JobScheduler/WorkManager over AlarmManager
  • Reduce polling; use push notifications
  • Follow Doze and App Standby guidelines

Q: What are Doze and App Standby?

Doze: When device is stationary and screen off, system defers jobs, syncs, alarms. Apps get periodic maintenance windows.

App Standby: Apps not recently used get restricted network and job access.

Handle by using WorkManager with appropriate constraints—system manages execution.


7. Network Optimization

Q: How do you optimize network usage?

StrategyBenefit
CachingReduce redundant requests
CompressionSmaller payloads (gzip)
PaginationLoad only needed data
Connection poolingReuse connections (OkHttp default)
PrefetchingLoad before needed (WiFi)
BatchingCombine multiple requests

Q: How does HTTP caching work?

OkHttp respects Cache-Control headers. Configure cache directory and size. Server controls caching with headers:

  • max-age: Cache duration
  • no-cache: Revalidate each time
  • no-store: Never cache

8. Profiling Tools

Q: What tools are available for performance analysis?

ToolPurpose
Android Studio ProfilerCPU, memory, network, energy
Systrace / PerfettoSystem-wide tracing
Layout InspectorView hierarchy, Compose recomposition
GPU ProfilerRendering performance
MacrobenchmarkStartup and runtime benchmarks
StrictModeDetect I/O on main thread

Q: How do you use StrictMode effectively?

Enable in debug builds only:

if (BuildConfig.DEBUG) {
    StrictMode.setThreadPolicy(
        StrictMode.ThreadPolicy.Builder()
            .detectAll()
            .penaltyLog()
            .build()
    )
}

Catches disk/network access on main thread early in development.


Quick Reference

AreaKey Points
Memory LeaksNo static Context; null bindings; unregister listeners; use LeakCanary
ANRNo I/O on main thread; use coroutines; check traces.txt for debugging
UI PerformanceFlatten hierarchies; reduce overdraw; use DiffUtil; profile with Systrace
StartupLazy init; defer work; baseline profiles; minimize Application.onCreate()
BatteryBatch requests; use WorkManager; respect Doze; prefer push over polling
NetworkCache responses; compress payloads; paginate; batch requests

Testing


1. Testing Pyramid

Q: What is the testing pyramid and how does it apply to Android?

LayerSpeedScopeTools
Unit TestsFast (ms)Single class/functionJUnit, Mockito, Turbine
Integration TestsMediumMultiple componentsRobolectric, Room testing
UI TestsSlow (s)Full app/flowsEspresso, Compose Testing

Aim for: Many unit tests, some integration tests, few UI tests.

Q: What should each layer test?

  • Unit: ViewModel logic, Repository, Use Cases, data transformations
  • Integration: Room DAOs, Repository with real database, navigation
  • UI: Critical user flows, screen state rendering, accessibility

2. Unit Testing

Q: How do you test ViewModels?

  1. Create ViewModel with fake dependencies
  2. Trigger actions
  3. Assert on exposed state

Key considerations:

  • Use TestDispatcher for coroutine control
  • Use runTest for test coroutine scope
  • Use Turbine for Flow testing

Q: How do you test coroutines?

Use kotlinx-coroutines-test:

  • runTest: Test coroutine scope with virtual time
  • StandardTestDispatcher: Queues coroutines, runs with advanceUntilIdle()
  • UnconfinedTestDispatcher: Runs coroutines eagerly

Inject TestDispatcher via constructor or Hilt.

Q: How do you test Flows?

Use Turbine library:

viewModel.state.test {
    assertEquals(Loading, awaitItem())
    assertEquals(Success(data), awaitItem())
    cancelAndIgnoreRemainingEvents()
}

Or use first(), take(), toList() for simpler cases.


3. Mocking

Q: When should you mock vs use fakes?

FakesMocks
Real implementation for testingConfigured to return specific values
Easier to maintainCan verify interactions
Better for repositories, DAOsBetter for third-party dependencies
More readable testsTests coupled to implementation

Prefer fakes for your own code, mocks for things you don’t control.

Q: What mocking libraries are common?

  • Mockito-Kotlin: Most popular, mature
  • MockK: Kotlin-first, coroutine support
  • Fake implementations: Hand-written, no framework

Q: What should you NOT mock?

  • Data classes / value objects
  • Your own simple classes (use real instances)
  • Android framework classes (use Robolectric or real device)

4. Testing Room

Q: How do you test Room DAOs?

Use in-memory database:

@Before
fun setup() {
    db = Room.inMemoryDatabaseBuilder(
        context, AppDatabase::class.java
    ).allowMainThreadQueries().build()
    dao = db.userDao()
}

@After
fun teardown() {
    db.close()
}

Test actual SQL queries—don’t mock Room.

Q: What should DAO tests verify?

  • Insert and query return correct data
  • Update modifies existing records
  • Delete removes records
  • Queries filter correctly
  • Flow emits on data changes

5. UI Testing

Q: What’s the difference between Espresso and Compose Testing?

EspressoCompose Testing
View-based UICompose UI
Find by ID, text, content descriptionFind by semantic properties
onView(withId(...)).perform(click())onNodeWithText(...).performClick()

Q: How do you structure Compose UI tests?

@Test
fun showsLoadingThenContent() {
    composeTestRule.setContent {
        MyScreen(viewModel = fakeViewModel)
    }
    
    // Assert loading state
    composeTestRule.onNodeWithTag("loading").assertIsDisplayed()
    
    // Trigger loaded state
    fakeViewModel.setLoaded(data)
    
    // Assert content
    composeTestRule.onNodeWithText("Title").assertIsDisplayed()
}

Q: What makes UI tests reliable?

  • Use waitFor / waitUntil for async operations
  • Use test tags for stable identifiers (not user-facing text)
  • Inject controlled ViewModels/repositories
  • Disable animations in test device/emulator
  • Keep tests focused on one scenario

6. Testing Strategies

Q: What’s the “Given-When-Then” pattern?

@Test
fun `user login with valid credentials succeeds`() {
    // Given: preconditions
    val fakeRepo = FakeAuthRepository(validCredentials = true)
    val viewModel = LoginViewModel(fakeRepo)
    
    // When: action
    viewModel.login("[email protected]", "password123")
    
    // Then: expected result
    assertEquals(LoginState.Success, viewModel.state.value)
}

Makes tests readable and self-documenting.

Q: How do you test error handling?

@Test
fun `network error shows error state`() = runTest {
    fakeRepository.setShouldThrowError(true)
    
    viewModel.loadData()
    
    val state = viewModel.state.value
    assertTrue(state is UiState.Error)
    assertEquals("Network error", (state as UiState.Error).message)
}

Test both happy path and error paths.

Q: What about testing navigation?

Options:

  1. Mock NavController, verify navigate() called
  2. Test with TestNavHostController
  3. For Compose: use NavHost in test, assert current destination

Keep navigation tests integration-level (not unit tests).


7. Test Doubles

Q: Explain different test doubles.

TypePurpose
FakeWorking implementation with shortcuts (in-memory DB)
StubReturns predetermined values
MockRecords calls, verifies interactions
SpyWraps real object, tracks calls
DummyPlaceholder, never actually used

Q: Example of a Fake Repository?

class FakeUserRepository : UserRepository {
    private val users = mutableListOf<User>()
    
    override suspend fun getUsers(): List<User> = users
    
    override suspend fun addUser(user: User) {
        users.add(user)
    }
    
    fun clear() = users.clear()
    fun seed(list: List<User>) = users.addAll(list)
}

Simple, controllable, no mocking framework needed.


8. Testing Best Practices

Q: What makes a good unit test?

  • Fast: Milliseconds, not seconds
  • Independent: No order dependency
  • Repeatable: Same result every time
  • Self-validating: Pass or fail, no manual inspection
  • Timely: Written alongside production code

Q: What’s code coverage and how much is enough?

Code coverage measures which code paths are exercised by tests. 70-80% is typical target. But:

  • 100% coverage doesn’t mean good tests
  • Behavior coverage matters more than line coverage
  • Focus on critical paths and edge cases

Q: How do you test legacy code without tests?

  1. Write characterization tests (document current behavior)
  2. Identify seams for dependency injection
  3. Extract testable components
  4. Add tests before refactoring
  5. Refactor with test safety net

Quick Reference

TopicKey Points
PyramidMany unit, some integration, few UI tests
Unit TestsFast, isolated, test logic not frameworks
CoroutinesrunTest, TestDispatcher, Turbine for Flows
MockingPrefer fakes for own code; mocks for third-party
RoomIn-memory database; test real SQL
UI TestsCompose Testing for Compose; Espresso for Views; disable animations
Best PracticesFIRST principles; Given-When-Then; test behavior not implementation

Security


1. Secure Data Storage

Q: Where should sensitive data be stored on Android?

Data TypeStorageNotes
Encryption keysAndroid KeystoreHardware-backed on supported devices
User credentialsEncryptedSharedPreferencesKeys in Keystore
Auth tokensEncryptedSharedPreferences or DataStoreNever plain SharedPreferences
Sensitive filesEncrypted files with Keystore-derived keys
DatabaseSQLCipher or Room with encryption

Q: What is Android Keystore?

A system-level secure container for cryptographic keys. Keys can be:

  • Hardware-backed (TEE/Secure Element) on supported devices
  • Bound to user authentication (biometric/PIN required)
  • Non-exportable (key material never leaves secure hardware)

Q: How does EncryptedSharedPreferences work?

Uses two keys from Keystore:

  1. Key encryption key (KEK) encrypts data keys
  2. Data encryption keys encrypt actual values

Both key names and values are encrypted at rest.


2. Network Security

Q: What is SSL/TLS pinning and why use it?

Pinning validates the server’s certificate against a known value, not just the certificate chain. Protects against:

  • Compromised Certificate Authorities
  • Man-in-the-middle attacks with rogue certs
  • Malicious proxies with user-installed certs

Q: How do you implement certificate pinning?

OkHttp CertificatePinner:

val pinner = CertificatePinner.Builder()
    .add("api.example.com", "sha256/AAAAAAA...")
    .add("api.example.com", "sha256/BBBBBBB...") // Backup pin
    .build()

Network Security Config (preferred):

<domain-config>
    <domain includeSubdomains="true">api.example.com</domain>
    <pin-digest algorithm="SHA-256">base64hash=</pin-digest>
</domain-config>

Q: What’s the risk of certificate pinning?

If pins expire or rotate without app update, the app breaks. Mitigations:

  • Always include backup pins
  • Use Network Security Config (updatable)
  • Plan for emergency pin rotation
  • Monitor certificate expiration

3. Authentication & Authorization

Q: How should auth tokens be handled?

  • Store in EncryptedSharedPreferences
  • Never log tokens
  • Use short-lived access tokens + refresh tokens
  • Clear tokens on logout
  • Handle token expiration gracefully

Q: How do you implement secure biometric authentication?

Use BiometricPrompt with:

  • setAllowedAuthenticators() to specify biometric class
  • CryptoObject to tie authentication to cryptographic operation
  • Keystore key that requires user authentication

Key is unusable until biometric succeeds—actual security, not just UI.

Q: What authentication classes exist?

ClassSecurity LevelExamples
Class 3 (Strong)HighestFingerprint, face with depth sensing
Class 2 (Weak)MediumSome face recognition
Class 1LowestPattern, PIN (not biometric)

For sensitive operations, require Class 3.


4. App Security

Q: How do you protect against reverse engineering?

TechniquePurpose
ProGuard/R8Code shrinking, obfuscation
String encryptionHide hardcoded secrets
Anti-tamperingDetect modified APK
Root detectionDetect compromised device
Emulator detectionDetect analysis environment

Q: What shouldn’t be in the APK?

  • API keys with billing access
  • Private encryption keys
  • Hardcoded passwords
  • Production database credentials

These can be extracted. Use server-side validation for sensitive operations.

Q: How do you handle API keys?

  • Store in local.properties (gitignored)
  • Inject via BuildConfig at build time
  • For sensitive keys, proxy through your backend
  • Use Play App Signing for signing keys

5. Input Validation

Q: What input validation is necessary?

  • Validate all user input on client AND server
  • Sanitize content for WebView (JavaScriptEnabled carefully)
  • Validate deep links and intent data
  • Escape SQL if not using Room (Room parameterizes)
  • Validate file paths to prevent path traversal

Q: What is a SQL injection and how does Room prevent it?

Malicious SQL in user input can modify queries. Room prevents by:

  • Parameterized queries (values can’t become SQL)
  • Compile-time SQL verification
  • Type-safe query parameters

Q: What about WebView security?

  • Disable JavaScript unless necessary
  • Use WebViewAssetLoader for local content
  • Don’t enable setAllowFileAccessFromFileURLs
  • Validate URLs before loading
  • Handle shouldOverrideUrlLoading carefully

6. Inter-Process Communication

Q: How do you secure exported components?

ComponentProtection
Activityandroid:exported="false" or permission
ServiceUse bound service with permission
BroadcastReceiverCustom permission, signature level
ContentProviderRead/write permissions, path permissions

Q: What are signature-level permissions?

Only apps signed with the same key can obtain the permission. Use for communication between your own apps.

Q: How do you validate incoming Intents?

  • Check callingPackage if applicable
  • Validate all extras (don’t trust input)
  • Use PendingIntent.FLAG_IMMUTABLE when possible
  • Don’t pass sensitive data in Intent extras

7. Runtime Security

Q: What is SafetyNet/Play Integrity?

Server-side API to verify:

  • Device is genuine (not rooted/emulated)
  • App is legitimate (from Play Store)
  • Device passes security checks

Use for sensitive operations; response should be verified server-side.

Q: How do you detect rooted devices?

Check for:

  • su binary existence
  • Root management apps
  • System property modifications
  • Dangerous permissions granted

Note: Determined attackers can bypass; use as defense in depth, not sole protection.


8. Common Vulnerabilities

Q: What are OWASP Mobile Top 10 concerns for Android?

RiskMitigation
Insecure Data StorageEncrypted storage, avoid logs
Insecure CommunicationTLS everywhere, pinning
Insecure AuthenticationStrong auth, biometrics
Insufficient CryptographyUse Android Keystore, standard algorithms
Insecure AuthorizationServer-side validation
Client Code QualityCode review, static analysis
Code TamperingProGuard, integrity checks
Reverse EngineeringObfuscation, server-side logic
Extraneous FunctionalityRemove debug code in release
Insufficient Binary ProtectionNative code obfuscation

Quick Reference

TopicKey Points
Data StorageKeystore for keys; EncryptedSharedPreferences for secrets; never plain text
NetworkHTTPS only; certificate pinning with backups; validate server responses
AuthShort-lived tokens; secure storage; biometric with CryptoObject
APK ProtectionR8 obfuscation; no secrets in code; server-side sensitive logic
Input ValidationClient and server validation; Room for SQL safety; careful WebView
IPCMinimal exported components; signature permissions; validate intents
RuntimePlay Integrity for verification; defense in depth against rooting

Build System


1. Gradle Basics

Q: What is Gradle and how does it work in Android?

Gradle is the build automation tool for Android. It:

  • Compiles source code
  • Manages dependencies
  • Creates APK/AAB packages
  • Runs tests
  • Signs and optimizes releases

Android uses the Android Gradle Plugin (AGP) which adds Android-specific tasks.

Q: What are the key Gradle files?

FilePurpose
settings.gradle.ktsDefines modules in project
build.gradle.kts (project)Project-wide config, plugin versions
build.gradle.kts (module)Module-specific config, dependencies
gradle.propertiesBuild settings, JVM config
local.propertiesLocal machine config (SDK path, secrets)

Q: Groovy DSL vs Kotlin DSL?

GroovyKotlin
Older, more examplesType-safe, IDE support
Dynamic typingStatic typing, autocomplete
.gradle extension.gradle.kts extension

Kotlin DSL is now recommended for new projects.


2. Build Variants

Q: What are build types and product flavors?

Build types define how the app is built:

  • debug: Debuggable, no optimization, debug signing
  • release: Optimized, minified, production signing

Product flavors define different versions:

  • free / paid
  • demo / full
  • staging / production

Variants = Build Type × Product Flavors (e.g., freeDebug, paidRelease)

Q: What are common build type configurations?

PropertyDebugRelease
isDebuggabletruefalse
isMinifyEnabledfalsetrue
isShrinkResourcesfalsetrue
signingConfigdebugrelease
applicationIdSuffix.debug(none)

Q: How do you configure different API endpoints per flavor?

Use buildConfigField:

productFlavors {
    create("staging") {
        buildConfigField("String", "API_URL", "\"https://staging.api.com\"")
    }
    create("production") {
        buildConfigField("String", "API_URL", "\"https://api.com\"")
    }
}

Access via BuildConfig.API_URL in code.


3. Dependencies

Q: Explain dependency configurations.

ConfigurationMeaning
implementationCompile and runtime, not exposed to consumers
apiCompile and runtime, exposed to consumers
compileOnlyCompile only, not in runtime
runtimeOnlyRuntime only, not for compilation
testImplementationFor unit tests
androidTestImplementationFor instrumented tests

Q: implementation vs api?

  • implementation: Dependency is internal, doesn’t leak to consumers
  • api: Dependency is part of public API, consumers can use it

Prefer implementation—faster builds, better encapsulation.

Q: How do you handle dependency conflicts?

  • Gradle uses highest version by default
  • Use constraints to force specific version
  • Use exclude to remove transitive dependencies
  • Check conflicts with ./gradlew dependencies
  • Use version catalogs for consistency

4. Version Catalogs

Q: What are version catalogs?

Centralized dependency management in libs.versions.toml:

[versions]
kotlin = "1.9.20"
compose = "1.5.4"

[libraries]
compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "compose" }

[bundles]
compose = ["compose-ui", "compose-material", "compose-foundation"]

[plugins]
kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

Usage: implementation(libs.compose.ui)

Benefits: Single source of truth, type-safe, IDE support.


5. ProGuard/R8

Q: What is R8 and what does it do?

R8 is Android’s code shrinker (replaced ProGuard). It:

  • Shrinks: Removes unused code
  • Optimizes: Inlines methods, removes dead branches
  • Obfuscates: Renames classes/methods to short names
  • Reduces APK size: Significant size reduction

Q: What are ProGuard/R8 rules?

Rules tell R8 what to keep:

  • -keep class com.example.Model: Don’t remove or rename
  • -keepnames class ...: Don’t rename (can still remove if unused)
  • -keepclassmembers: Keep members, not class itself

Q: When are keep rules needed?

  • Reflection (classes loaded by name)
  • Serialization (Gson, JSON parsing)
  • JNI (native methods)
  • Custom views in XML
  • Libraries without rules

Most libraries include their own rules via consumerProguardFiles.


6. Signing

Q: What is APK/AAB signing?

Digital signature that:

  • Proves app comes from you
  • Ensures APK hasn’t been modified
  • Required for Play Store and device installation
  • Same key required for updates

Q: Debug vs Release signing?

DebugRelease
Auto-generated debug keystoreYour production keystore
Same on all machinesMust be protected
Can’t publish to Play StoreRequired for Play Store

Q: What is Play App Signing?

Google manages your app signing key:

  • You upload with upload key
  • Google re-signs with app signing key
  • If upload key compromised, can reset it
  • Key never leaves Google’s servers

Recommended for all new apps.


7. Build Performance

Q: How do you speed up Gradle builds?

SettingEffect
org.gradle.parallel=trueBuild modules in parallel
org.gradle.caching=trueCache task outputs
org.gradle.daemon=trueKeep Gradle process running
kapt.incremental.apt=trueIncremental annotation processing
Increase JVM heaporg.gradle.jvmargs=-Xmx4g

Q: What is incremental compilation?

Only recompile files that changed. Kotlin supports it, but annotation processors (KAPT) can break it. Consider KSP (Kotlin Symbol Processing) for better incremental support.

Q: How do you analyze build performance?

  • ./gradlew --profile: Generate HTML report
  • Build Analyzer in Android Studio
  • --scan for detailed Gradle build scan
  • Check configuration phase time (runs every build)

8. Modularization

Q: Why modularize an Android project?

  • Faster builds: Only changed modules rebuild
  • Better encapsulation: Clear boundaries, internal visibility
  • Parallel development: Teams work independently
  • Reusability: Share modules across apps
  • Dynamic delivery: Feature modules on demand

Q: What module types exist?

TypePurpose
appMain application module
libraryShared code, produces AAR
featureSelf-contained feature
coreCommon utilities, base classes
dataRepository, data sources
domainBusiness logic, use cases

Q: What’s dynamic feature delivery?

Feature modules delivered on-demand or by condition:

  • Install-time: Included in base APK
  • On-demand: Downloaded when needed
  • Conditional: Based on device features

Uses App Bundle format, Play Store handles delivery.


Quick Reference

TopicKey Points
Gradle Filessettings.gradle.kts (modules), build.gradle.kts (config), gradle.properties (settings)
Build VariantsBuild types × Flavors; debug/release × free/paid
Dependenciesimplementation (internal), api (exposed), version catalogs for consistency
R8Shrink, optimize, obfuscate; keep rules for reflection/serialization
SigningDebug auto-generated; release keystore protected; Play App Signing recommended
Build SpeedParallel, caching, daemon; avoid KAPT, use KSP; analyze with –profile
ModularizationFaster builds, encapsulation, parallel development; app/library/feature/core/data modules

System Design


1. Approaching Design Questions

Q: How do you approach a mobile system design question?

Follow this structure:

  1. Clarify Requirements (2-3 min)

    • Functional: What features?
    • Non-functional: Offline? Scale? Battery?
    • Constraints: Platform? API limitations?
  2. High-Level Design (5-10 min)

    • Architecture diagram
    • Main components
    • Data flow
  3. Deep Dive (10-15 min)

    • Specific component design
    • Data models
    • Key algorithms
  4. Trade-offs & Extensions (5 min)

    • What you’d do differently
    • Scalability considerations
    • Edge cases

Q: What non-functional requirements matter for mobile?

RequirementConsiderations
Offline SupportLocal caching, sync strategy, conflict resolution
PerformanceStartup time, scroll performance, memory usage
BatteryNetwork batching, background work limits
BandwidthCompression, pagination, delta updates
SecurityData encryption, secure communication

2. News Feed / Social Feed

Q: Design a news feed like Twitter or Instagram.

Key Components:

  • Feed Repository: Single source of truth, coordinates remote/local
  • Paging: Infinite scroll with Paging 3
  • Caching: Room database as cache
  • Real-time: WebSocket or polling for new items

Architecture:

UI (LazyColumn) → ViewModel → Repository
                                 ├── RemoteMediator → API
                                 └── PagingSource → Room

Key Decisions:

  • Cursor vs Offset pagination: Cursor is stable (items don’t shift)
  • Cache invalidation: Time-based or on pull-to-refresh
  • Optimistic updates: Show locally before server confirms
  • Image loading: Preload next page images

Trade-offs:

  • More caching = better UX, more storage/complexity
  • WebSocket = real-time, more battery/complexity

3. Messaging App

Q: Design a chat/messaging app like WhatsApp.

Key Components:

  • Message Repository: Handles send/receive/sync
  • WebSocket/XMPP: Real-time message delivery
  • Local Database: All messages stored locally
  • Background Service: Receive messages when app closed

Data Model:

Conversation: id, participants, lastMessage, unreadCount
Message: id, conversationId, sender, content, timestamp, status

Key Decisions:

  • Delivery guarantees: At-least-once with deduplication
  • Message status: Sent → Delivered → Read
  • Offline messages: Queue locally, sync when online
  • End-to-end encryption: Signal Protocol pattern

Sync Strategy:

  1. On app start, fetch missed messages since last sync
  2. WebSocket for real-time during session
  3. Push notification triggers sync when app closed

4. Image Loading Library

Q: Design an image loading library like Coil or Glide.

Key Components:

  • Request Queue: Prioritize visible images
  • Memory Cache: LruCache for decoded bitmaps
  • Disk Cache: DiskLruCache for downloaded files
  • Decoder: BitmapFactory with sampling
  • Transformations: Resize, crop, rounded corners

Flow:

Request → Memory Cache? → Disk Cache? → Network
    ↓           ↓              ↓            ↓
   Done     Decode         Decode       Download
                ↓              ↓            ↓
          Cache in         Cache in    Cache both
           Memory           Memory

Key Decisions:

  • Memory cache size: ~1/8 of available memory
  • Disk cache size: ~50-250MB configurable
  • Request cancellation: Cancel when view recycled
  • Placeholder/Error: Immediate feedback

Optimizations:

  • Downsampling: Load at target size, not full resolution
  • Request coalescing: Same URL = single request
  • Preloading: Load upcoming images in list

5. Offline-First App

Q: How do you design an offline-first application?

Principles:

  1. Local database is source of truth
  2. All operations work offline
  3. Sync when connectivity available
  4. Handle conflicts gracefully

Architecture:

UI → ViewModel → Repository (SSOT)
                      ↓
                 Local DB ←→ SyncManager → API

Sync Strategies:

StrategyApproachUse Case
Last-write-winsLatest timestamp winsSimple, some data loss ok
Server-winsServer always correctAuthoritative server data
Client-winsClient always correctUser edits are precious
Manual resolutionUser choosesCollaborative editing
CRDTAutomatic mergeComplex, no conflicts

Key Decisions:

  • Change tracking: Track local modifications since last sync
  • Conflict detection: Compare versions/timestamps
  • Partial sync: Only sync changed data
  • Background sync: WorkManager with network constraint

6. Video Streaming

Q: Design a video streaming app like YouTube/Netflix.

Key Components:

  • Player: ExoPlayer with adaptive streaming
  • Caching: Pre-cache upcoming segments
  • Download: Background download for offline
  • CDN: Multiple quality levels, nearby servers

Streaming Protocol:

  • HLS/DASH: Adaptive bitrate streaming
  • Segments downloaded progressively
  • Quality adjusts to network conditions

Key Decisions:

  • Buffer size: Balance memory vs smooth playback
  • Quality selection: Auto (ABR) or user choice
  • Preloading: Start loading next video early
  • Resume position: Save and restore playback position

Offline Viewing:

  • Download in background with WorkManager
  • Encrypt downloaded content (DRM)
  • Track download progress, allow pause/resume
  • Expire after license period

7. Location-Based App

Q: Design a ride-sharing or delivery app.

Key Components:

  • Location Service: Foreground service with updates
  • Map Integration: Google Maps or Mapbox
  • Real-time Updates: Driver location via WebSocket
  • Geofencing: Trigger events at locations

Battery Considerations:

  • Use significant location change (not continuous)
  • Batch location updates
  • Reduce frequency when stationary
  • Foreground service required for background location

Key Decisions:

  • Location accuracy: Fused Location Provider, balance power/accuracy
  • Update frequency: Every 5-10 seconds active, less in background
  • Predictive routing: Update ETA based on traffic
  • Offline maps: Cache tiles for core areas

8. E-Commerce App

Q: Design a shopping app like Amazon.

Key Components:

  • Product Catalog: Search, browse, filter
  • Cart: Local + synced
  • Checkout: Payment integration
  • Order Tracking: Status updates

Architecture Decisions:

  • Search: Debounce input, paginate results
  • Cart: Optimistic local updates, sync to server
  • Product Cache: Cache popular items, invalidate on changes
  • Deep Linking: Open product from notification/share

Key Features:

  • Recently Viewed: Local history
  • Recommendations: Personalized from server
  • Wishlist: Local with sync
  • Reviews: Lazy load, paginate

9. Design Patterns for Mobile

Q: What patterns come up repeatedly in mobile system design?

PatternProblemSolution
RepositoryCoordinate data sourcesSingle source of truth
Cache-asideReduce network callsCheck cache, fallback to network
Retry with backoffHandle transient failuresExponential backoff
Optimistic updateResponsive UIUpdate local, sync later
Event sourcingAudit, undo, offlineStore events, derive state
CQRSDifferent read/write needsSeparate models

Quick Reference

ScenarioKey Considerations
News FeedPaging, caching, cursor pagination, image preloading
MessagingWebSocket, local DB, delivery status, encryption
Image LoadingMemory/disk cache, cancellation, downsampling
Offline-FirstLocal SSOT, sync strategy, conflict resolution
VideoAdaptive streaming, buffering, DRM, background download
LocationBattery, foreground service, update frequency
E-CommerceSearch, cart sync, deep linking, recommendations

AOSP & Android Internals


1. Android Architecture

Q: Explain the Android system architecture.

Android is a layered system stack:

LayerComponentsLanguage
ApplicationsSystem apps, third-party appsKotlin/Java
FrameworkActivityManager, PackageManager, WindowManagerJava
Native Librarieslibc, SSL, SQLite, MediaC/C++
Android RuntimeART, core librariesC++/Java
HALCamera, Audio, SensorsC/C++
Linux KernelDrivers, Binder, power managementC

Q: What does each layer do?

  • Framework: Provides APIs developers use (Activities, Views, Services)
  • ART: Executes app code, manages memory, garbage collection
  • HAL: Abstracts hardware differences—same API for different devices
  • Kernel: Core OS—process scheduling, memory, drivers

2. Boot Process

Q: Describe the Android boot sequence.

  1. Boot ROM: First code, loads bootloader
  2. Bootloader: Initializes hardware, loads kernel
  3. Kernel: Initializes drivers, mounts filesystem, starts init
  4. Init: First user process, parses init.rc, starts daemons
  5. Zygote: Preloads classes, spawns app processes
  6. System Server: Starts all system services (AMS, PMS, WMS)
  7. Launcher: Home screen app starts

Q: What is Zygote and why does it exist?

Zygote is the parent of all app processes. It:

  • Preloads ~4000 common classes
  • Preloads common resources
  • Forks to create new app processes

Benefits:

  • Fast app startup (classes already loaded)
  • Memory sharing (Copy-on-Write)
  • Consistent runtime environment

Q: What does System Server do?

Starts and manages all system services:

  • ActivityManagerService (AMS): Manages activities, apps
  • PackageManagerService (PMS): Installs, queries packages
  • WindowManagerService (WMS): Manages windows, input
  • 100+ other services

Each service runs in a Binder thread pool within System Server process.


3. Binder IPC

Q: What is Binder and why does Android use it?

Binder is Android’s inter-process communication mechanism. Apps run in isolated processes; Binder allows them to communicate with system services and each other.

Why not traditional IPC?

  • Security: Built-in caller UID/PID verification
  • Performance: Single copy (not two like pipes)
  • Object-oriented: RPC semantics, pass object references
  • Reference counting: Automatic cleanup

Q: How does a Binder call work?

  1. Client calls method on Proxy object
  2. Proxy marshals parameters into Parcel
  3. Binder driver transfers to server process
  4. Stub unmarshals and calls real implementation
  5. Result marshaled back through Binder
  6. Proxy returns result to client

All app-to-system communication uses Binder (getSystemService → Binder).

Q: What is AIDL?

Android Interface Definition Language. Generates Proxy/Stub code for Binder communication. You define the interface, AIDL generates the IPC boilerplate.


4. Activity Manager Service

Q: What is ActivityManagerService (AMS)?

The core system service managing:

  • Activity lifecycle and task stacks
  • Process lifecycle and LRU management
  • App launch and Intent resolution
  • Recent apps list
  • Foreground/background state

Q: How does app launch work internally?

  1. Launcher calls startActivity(Intent)
  2. Intent goes to AMS via Binder
  3. AMS resolves Intent to target Activity
  4. AMS checks if process exists
  5. If not, asks Zygote to fork new process
  6. New process starts ActivityThread.main()
  7. App attaches to AMS, AMS schedules Activity creation
  8. ActivityThread creates Activity, calls onCreate()

Q: How does AMS decide which processes to kill?

Maintains LRU list with process importance:

  1. Foreground (visible Activity)
  2. Visible (partially visible)
  3. Service (background service)
  4. Cached (background activities)
  5. Empty (no components)

Kills from bottom up when memory needed.


5. PackageManagerService

Q: What does PackageManagerService (PMS) do?

  • Installs/uninstalls packages
  • Parses and stores package metadata
  • Resolves Intent to components
  • Manages permissions
  • Maintains package database

Q: What happens during APK installation?

  1. APK copied to staging area
  2. Signature verification
  3. Manifest parsing
  4. Native library extraction
  5. DEX optimization (dex2oat)
  6. Package added to PMS database
  7. Broadcast PACKAGE_ADDED

Q: How does Intent resolution work?

  1. App calls startActivity(intent)
  2. PMS scans registered IntentFilters
  3. Matches action, category, data against filters
  4. Returns matching components
  5. If multiple, shows chooser
  6. AMS launches chosen component

6. Android Runtime (ART)

Q: What is ART and how does it differ from Dalvik?

DalvikART
JIT (Just-In-Time)AOT + JIT (Ahead-Of-Time)
Compiled at runtimeCompiled during install
Slower startupFaster runtime
Less storageMore storage (compiled code)

ART also has:

  • Better garbage collection (concurrent)
  • Improved profiling and debugging
  • Profile-guided compilation

Q: What is DEX and why is it used?

DEX (Dalvik Executable) is Android’s bytecode format:

  • Optimized for memory-constrained devices
  • More compact than Java bytecode
  • Register-based (not stack-based)
  • Single DEX = all classes (vs separate .class files)

dex2oat compiles DEX to native code during install.

Q: How does garbage collection work in ART?

  • Concurrent: Most work while app runs
  • Generational: Young objects collected more often
  • Compacting: Reduces fragmentation
  • Card marking: Track cross-generation references

GC pauses are minimal (<5ms typically).


7. Window System

Q: How does the Android window system work?

WindowManagerService (WMS) manages:

  • Window creation and positioning
  • Z-ordering of windows
  • Input routing to correct window
  • Surface allocation

SurfaceFlinger (native):

  • Composites surfaces from apps
  • Sends to display hardware
  • Handles VSync timing

Q: What is a Surface?

Buffer that apps draw to. Each window has a Surface:

  • App draws to Surface via Canvas or OpenGL
  • Surface is shared memory with SurfaceFlinger
  • SurfaceFlinger composites all Surfaces
  • Result sent to display

Q: How does hardware acceleration work?

  • App builds display list (recording of draw operations)
  • RenderThread processes display list on GPU
  • Canvas operations translated to OpenGL
  • Frees main thread for other work

8. HAL and Treble

Q: What is HAL?

Hardware Abstraction Layer—interface between Android framework and device hardware. Standardized API regardless of underlying hardware:

  • Camera HAL: Same API, different camera chips
  • Audio HAL: Same API, different audio hardware
  • Sensor HAL: Same API, different sensors

Q: What is Project Treble?

Architecture change in Android 8.0:

  • Separates vendor implementation from framework
  • HAL implementations become HIDL services
  • Framework can update without touching vendor code
  • Enables faster Android updates on devices

Before: HAL directly linked into framework After: HAL as separate processes, communication via HIDL/AIDL


9. Security Architecture

Q: How does Android’s security model work?

App Sandbox:

  • Each app runs as separate Linux user
  • Private data directory per app
  • Can’t access other apps’ data
  • SELinux provides mandatory access control

Permissions:

  • Declared in manifest
  • User grants at install or runtime
  • System enforces at API level

Verified Boot:

  • Each boot stage verifies next stage
  • Detects system modification
  • Rollback protection

Q: What is SELinux on Android?

Mandatory Access Control—even root can’t bypass:

  • Policies define what each domain can access
  • Apps run in app domain, limited access
  • System services have specific domains
  • Blocks many exploit techniques

10. Common Interview Topics

Q: What’s asked in AOSP/internals interviews?

TopicFocus Areas
Boot ProcessSequence, Zygote role, System Server
BinderHow IPC works, AIDL, Parcelable
AMSApp launch, process management, task stack
PMSInstallation, Intent resolution, permissions
ARTDEX, AOT/JIT, garbage collection
Window SystemWMS, Surface, SurfaceFlinger
SecuritySandbox, permissions, SELinux

Q: Why do companies ask about internals?

  • Platform engineering roles require it
  • Debugging complex issues needs system knowledge
  • Performance optimization requires understanding internals
  • Security work needs deep knowledge
  • Shows depth of understanding beyond SDK

Quick Reference

TopicKey Points
ArchitectureApps → Framework → Native/ART → HAL → Kernel
BootROM → Bootloader → Kernel → Init → Zygote → System Server → Launcher
BinderIPC mechanism; Proxy/Stub pattern; single-copy, secure
AMSManages activities, processes, app lifecycle; LRU for killing
PMSInstalls packages, resolves intents, manages permissions
ARTAOT+JIT compilation; concurrent GC; DEX bytecode format
WindowsWMS manages windows; SurfaceFlinger composites; Surface is draw buffer
SecurityApp sandbox (Linux users); SELinux; verified boot