71 Android interview questions with detailed answers, sorted from basic to advanced. Free to practice, with progress saved automatically.
Android development typically uses Kotlin with the Jetpack libraries to build apps for devices running the Android operating system. The questions span Kotlin from val and var to inline and reified functions, the Activity lifecycle, Jetpack Compose versus XML layouts, MVVM architecture, Room, RecyclerView, Gradle and ProGuard, and app startup performance.
val declares a read-only variable that cannot be reassigned after initialization (like final in Java), while var declares a mutable variable. Note: val only forbids reassigning the reference; it does not make the object itself immutable: To keep data from being modified, combine val with…
Null safety in Kotlin means the type itself says whether a variable can be null. String is non-nullable: assigning null is a compile error. String? is nullable: null is allowed, but the compiler forces you to handle the null case before using it. Ways to…
Extension functions allow you to add new methods to existing classes without inheritance or modifying the original class. For example, on String: The function name is prefixed with the class you're extending, and this refers to the calling instance. An extension does not actually modify…
A data class automatically generates boilerplate methods: equals(), hashCode(), toString(), copy() and the componentN() functions (component1(), component2()…) used for destructuring. These methods only use the properties declared in the primary constructor. Data classes require at least one constructor parameter and are a good fit for…
A sealed class restricts a class hierarchy to a fixed set of subclasses that the compiler knows at compile time. Since Kotlin 1.5, direct subclasses must live in the same module and package as the sealed class (before 1.5 they had to be in the…
Scope functions run a lambda on an object, grouping the code that works with that object into one block. The five differ in two ways: whether the lambda refers to the object as this or it, and whether the function returns the lambda result or…
A coroutine is a block of code that can suspend and resume later, without holding a thread while it waits. That lets you write asynchronous code top to bottom, instead of nesting callbacks. getUser is a suspend function: the coroutine pauses there, hands the main…
launch is a fire-and-forget coroutine that returns a Job and no result. async is used when you need to perform a task and get its result back; it returns a Deferred, and you must call await() on it to retrieve the result. Use launch for…
A suspend function can pause without blocking its thread, then continue once the result is ready. It can only be called from a coroutine or from another suspend function. Mechanism: the compiler turns a suspend function into one that takes an extra hidden Continuation parameter…
Exceptions in coroutines follow the parent-child tree (structured concurrency): a failing child reports to its parent, and how the failure is handled depends on the builder (launch or async) and the scope's Job type. The second coroutine does not "fail silently". The mechanics: - launch…
A companion object allows you to define static-like members (constants, factory methods) within a class that belong to the class itself, not instances. Only one companion object is allowed per class. You can call companion object members without creating an instance: MyClass.myStaticMethod(). It's similar to…
Destructuring lets you break an object into individual variables in a single statement, e.g. val (name, age) = person. Mechanism: the compiler turns that line into calls to component1(), component2()... by position, not by property name: - Data classes auto-generate componentN() for each property in…
The Activity lifecycle is the sequence of callbacks the system invokes as a screen is created, becomes visible, loses focus, gets covered and is destroyed: - onCreate(): one-time setup — setContent/setContentView, obtain the ViewModel, read savedInstanceState. - onStart(): the Activity becomes visible. - onResume(): in…
replace() removes the Fragments currently in the container and adds the new one in their place. add() keeps the existing Fragment and stacks the new one on top of it in the same container. What happens on back depends on addToBackStack: - replace() with addToBackStack:…
An Intent is a "request" object handed to the system to start a component: open an Activity, run a Service, send a broadcast. There are two types: - Explicit intent: names the exact target component — typically used inside your own app. - Implicit intent:…
A Service is a component for work that needs no UI. It runs on the process's main thread, so heavy work still has to be moved to a coroutine or another thread. Three forms: - Started service (startService): runs until it calls stopSelf() or is…
A BroadcastReceiver is a component that listens for system-wide broadcasts (like battery low, network changes, boot completed) or custom broadcasts sent by apps. You register it statically in the manifest or dynamically in code, and the system calls onReceive() when a matching broadcast arrives. Key…
A ContentProvider is a component that gives other apps or processes controlled access to an app's data. It uses URIs of the form content://authority/path to identify resources and implements CRUD operations through query(), insert(), update(), delete(). Other apps don't call a ContentProvider directly; they go…
Android groups permissions by risk: - Normal (INTERNET, VIBRATE): declared in the manifest and granted at install time. - Dangerous / runtime (camera, location, contacts, microphone, POSTNOTIFICATIONS since Android 13): since Android 6.0 (API 23) they must be requested at runtime; the user can deny…
They are two ends of the same mechanism: onSaveInstanceState(outState) is the callback where you write UI state into a Bundle; savedInstanceState is that same Bundle handed back when the Activity is recreated, through onCreate() (and onRestoreInstanceState()). The system calls onSaveInstanceState when the Activity might be…
An implicit intent only says what you want to do (action, data type) without naming a specific component, e.g. startActivity(Intent(Intent.ACTIONSEND).setType("text/plain")). Intent filters are declared in the manifest to specify which implicit intents an Activity can handle, defined by action, category and data type: When you…
A Process is an operating system process with its own memory, running independently of other processes. A Thread is an execution flow inside a process, and all threads in the same process share its memory. Process Thread --------- Memory Separate, isolated from other processes Shared…
Jetpack Compose is a declarative UI toolkit that lets you build Android UIs with Kotlin functions instead of XML. The difference is in how the UI gets updated. With XML (the imperative style), you inflate a layout, then find views and mutate them whenever data…
A Composable function is a Kotlin function annotated with @Composable that describes UI in Jetpack Compose: it takes data and "emits" UI elements. The only hard requirement: a composable can only be called from another composable (or from a composable lambda such as the content…
mutableStateOf() creates a MutableState<T that Compose observes: any composable reading .value is recomposed when the value changes. remember keeps that object across recompositions — without remember, every recomposition creates a fresh state reset to its initial value. Variants worth knowing: - rememberSaveable: survives rotation and…
remember keeps state across recompositions within the composable's scope but loses it when the Activity is recreated, e.g. on screen rotation. rememberSaveable stores the state in the saved instance state Bundle, so it survives configuration changes and process death (the system kills the app in…
State hoisting means moving state from a child composable to its parent, making the child stateless. A stateless composable receives state as a parameter (value) and a callback (onValueChange) to tell the parent when the state should change: When the user types, TextField calls onNameChange,…
LaunchedEffect is a side-effect API that launches a coroutine tied to the composable's lifecycle in the composition. Use it when you need to run a suspend function when a screen appears or when a value changes: loading data, running animations, sending analytics, showing a snackbar.…
A Modifier is a chainable object that adjusts a composable's size, layout, appearance (background, border) and interaction (click, scroll). Common modifiers: .size(100.dp), .background(Color.Blue), .padding(16.dp), .clickable { }. .weight(1f) only works inside a Row/Column, because it splits the remaining space of that Row/Column. Order matters because…
Use the Navigation Compose library with a NavController (holds the back stack, calls navigate()) and a NavHost (the area that shows the screen for the current route). Since Navigation 2.8.0 (stable in September 2024), routes should be declared type-safe with @Serializable classes instead of strings:…
Theming in Compose revolves around MaterialTheme (Material 3): you define colors (colorScheme), typography and shapes once, wrap the whole app in a theme composable, and every composable inside reads values through MaterialTheme.colorScheme and MaterialTheme.typography. - Dark/light mode: isSystemInDarkTheme() reads the system setting to pick darkColorScheme…
MVVM (Model-View-ViewModel) separates UI logic from business logic into three parts: - Model: holds data and business rules, usually repositories, Room, APIs. - View: displays the UI (Activity/Fragment/Composable). The View does not handle logic itself; it only renders state and sends user events up to…
The Repository pattern puts data sources (local database, remote API, cache) behind a single class. The ViewModel only asks the repository and does not need to know whether the data comes from Room or Retrofit. The ViewModel collects observeUser to show the data currently in…
Both hold a current value and notify observers when it changes, but they come from different places: LiveData is an Android class and lifecycle-aware out of the box; StateFlow is part of Kotlin coroutines and has no Android dependency. LiveData StateFlow --------- Initial value Optional…
A ViewModel is a screen's state holder: it keeps and prepares data for the UI and survives configuration changes (rotation, dark-mode switch) — the Activity is recreated but the ViewModel stays, so the API is not called again. Its proper role according to Android's architecture…
Dependency injection (DI) means an object receives the dependencies it needs from outside (usually through its constructor), instead of creating or fetching them itself. In the first version, a unit test cannot swap api for a fake, because the class calls the real Retrofit itself.…
Hilt is a DI library built on Dagger, designed specifically for Android. With plain Dagger you write components yourself, call inject in every Activity and manage their lifecycles by hand. Hilt does that for you: it generates a set of components tied to Android lifecycles,…
A scope in Hilt decides how long an instance is shared. By default every binding is unscoped: each injection gets a new instance. With a scope, Hilt creates one instance per instance of the matching component, and every injection inside that component shares it. Each…
Room is the Jetpack library on top of SQLite: you declare entities and queries with annotations, Room generates code at compile time (via KSP) and validates your SQL at build time. Compared with using SQLiteOpenHelper directly: - A wrong column name or SQL syntax →…
A DAO (Data Access Object) is an interface that declares database operations in Room. You mark each method with @Query, @Insert, @Update or @Delete, and Room generates the implementation at compile time. You only write the interface and the SQL; Room reads the annotations, generates…
Retrofit is a type-safe HTTP client that turns a REST API into a Kotlin interface. You declare an interface whose methods are annotated with HTTP verbs such as @GET and @POST, and Retrofit generates the implementation. When you call api.getUser(1) in a coroutine, Retrofit builds…
Interceptors sit in front of HTTP requests and responses so you can modify them. - addInterceptor registers an application interceptor, which runs once per request. addNetworkInterceptor { ... } registers a network interceptor, with the same lambda form but running for every actual network call…
Retrofit runs on OkHttp, so authentication lives at the OkHttp layer with two pieces: an Interceptor that adds Authorization: Bearer <token to every request, and an Authenticator that OkHttp calls when the server returns 401, to refresh the token and resend the request. Both are…
DataStore is the replacement for SharedPreferences, built on coroutines and Flow, with transactional writes. It comes in two flavors: - Preferences DataStore: stores key-value pairs like SharedPreferences, needs no schema, but is not type-safe at the object level (each key declares its own type). -…
WorkManager schedules deferrable background work that is guaranteed to run, even if the app is closed or the device reboots, because it persists requests in an internal database. It respects Doze Mode and App Standby, and uses JobScheduler under the hood (API 23+). Use it…
Paging 3 is a Jetpack library for loading a large list page by page as the user scrolls, instead of all at once. You only write how to load one page; deciding when to load the next one, caching, loading/error state and retry are handled…
The Observer pattern lets objects (observers) subscribe to state changes of another object (subject). The subject doesn't need to know who the observers are, it just announces "the data changed", which decouples the data source from the UI and lets several screens react to the…
On a configuration change (rotation, language change, dark mode toggle), the system destroys and recreates the Activity/Fragment to reload resources for the new configuration. Every field in the Activity is lost, so you pick where to keep state based on the kind of data: -…
Context is the bridge to system resources and services: accessing resources, starting Activities/Services, getSystemService(), opening databases, etc. Two main flavors differ by lifecycle: - Activity context: tied to one Activity's lifecycle and aware of theme/UI. Use it when correct theming matters: inflating layouts, creating Dialogs,…
An ANR is the system dialog shown when the main (UI) thread is blocked too long and the app stops responding. Key thresholds: - No response to an input event (touch, key) within 5 seconds. - A BroadcastReceiver.onReceive() runs too long (10 seconds foreground, 60…
RecyclerView efficiently renders large lists/grids by reusing a small pool of Views instead of creating one per item. Picture a window showing only a few rows: as you scroll, a row leaving the screen isn't destroyed — it goes into the RecycledViewPool and is re-bound…
Gradle is the automated build system Android uses to compile code, package resources, and produce APK/AAB files. The Android Gradle Plugin (AGP) extends Gradle with Android-specific tasks. Key roles: - Dependency management: declare libraries and Gradle downloads and resolves them automatically. - Build variants: combine…