Bryn Flow

🤖 Android Interview Questions & Answers

The questions that come up again and again in Android developer interviews, with concise, correct answers — grouped by difficulty.

← Android Track

Beginner Fundamentals

What is the difference between val and var in Kotlin?

val declares a read-only reference — it can be assigned once, at declaration or in an init block. var declares a mutable reference that can be reassigned later. Note that val only locks the reference, not the object it points to — a val list = mutableListOf(1) can still have items added to it.

What is the Activity lifecycle?

The set of callbacks Android calls as an Activity moves through states: onCreate (first creation) → onStart (becoming visible) → onResume (in the foreground) → onPause (partially obscured) → onStop (no longer visible) → onDestroy. onRestart fires when a stopped Activity becomes visible again, right before onStart.

What is the difference between an Activity and a Fragment?

An Activity is a single screen with its own window and entry in the system's task stack. A Fragment is a reusable portion of UI with its own lifecycle, hosted inside an Activity — used historically for multi-pane layouts. In modern Compose-first apps, fragments are largely replaced by composables and Navigation Compose.

What does @Composable mean in Jetpack Compose?

It marks a function as one that describes UI and can participate in Compose's recomposition system — the function may be re-invoked automatically whenever the state it reads changes, so Compose knows what to redraw.

What is the difference between LinearLayout and ConstraintLayout in the XML View system?

LinearLayout arranges children in a single row or column, nesting required for complex UIs. ConstraintLayout positions each child relative to other views or the parent using constraints, letting you build flat, complex layouts without deep nesting — better for performance and maintainability.

What is a null-safety operator like ?. or ?: used for?

?. (safe call) evaluates to null instead of throwing if the receiver is null, e.g. name?.length. ?: (Elvis operator) supplies a default when the left side is null, e.g. name?.length ?: 0. Together they let you handle nullable values without explicit if-null checks everywhere.

What is the difference between == and === in Kotlin?

== calls equals() and checks structural equality — whether two objects hold the same data. === checks referential equality — whether both variables point to the exact same object in memory. Two separate data class instances with identical fields are == but not ===.

What does a data class give you for free in Kotlin?

Declaring a class with data auto-generates equals()/hashCode() based on the primary constructor properties, a readable toString(), a copy() function for creating modified copies, and componentN() functions that enable destructuring like val (name, age) = user.

Intermediate Architecture & State

Why use a ViewModel instead of storing state directly in an Activity?

A ViewModel is scoped to its owner (Activity, Fragment, or nav graph) and survives configuration changes like screen rotation, so UI state isn't lost when the Activity is recreated. It also keeps UI logic separate from lifecycle-bound UI code, making it independently testable.

What is state hoisting in Jetpack Compose?

Moving state out of a composable and up to its caller, so the composable receives the current value and an event callback rather than owning the state itself. This makes composables stateless, reusable, and easy to preview or test with different data sources.

What is the difference between LiveData and StateFlow?

Both are observable holders of a current value. LiveData is lifecycle-aware out of the box and designed for the View system. StateFlow is part of Kotlin coroutines, works anywhere coroutines do (not just Android), always has an initial value, and integrates naturally with Compose via collectAsState(). Most new Compose code prefers StateFlow.

What does remember do, and how is it different from rememberSaveable?

remember caches a value across recompositions but loses it if the Activity is recreated (e.g. rotation) or the process dies. rememberSaveable additionally saves the value into the instance state bundle, so it survives configuration changes and process death, as long as the type is savable (primitives, Parcelable, etc.).

What is the purpose of a key parameter in LazyColumn's items()?

It gives Compose a stable identity for each item so it can correctly track insertions, removals, and reorders across recompositions — without it, Compose falls back to position-based identity, which can cause incorrect animations or lost item state when the list changes.

What is the difference between launch and async in coroutines?

launch starts a coroutine that returns a Job and doesn't produce a result — used for fire-and-forget work. async starts a coroutine that returns a Deferred<T>, whose result you retrieve with await() — used when you need a computed value, often to run multiple operations concurrently.

val a = async { fetchA() }
val b = async { fetchB() }
val combined = a.await() + b.await() // both run concurrently
What does Room do at compile time that raw SQLite doesn't give you?

Room validates your @Query SQL against your entity schema at compile time, so a typo'd column name or mismatched return type fails the build instead of crashing at runtime. It also generates the boilerplate for cursor mapping and integrates with Flow/LiveData for reactive queries.

Does the order of Compose modifiers matter?

Yes — each modifier wraps the previous result, so order changes the outcome. Modifier.padding(16.dp).background(Color.Red) paints the background inside the padding (the padding stays transparent), while Modifier.background(Color.Red).padding(16.dp) paints the background first and reserves padding inside it, so the red fills the full original size. Think of each modifier as nesting a new layout box around what came before.

Why are sealed classes a common choice for modeling UI state?

A sealed class (or sealed interface) restricts a type to a known, closed set of subclasses defined in the same file or module. Modeling screen state as sealed class UiState with Loading, Success(data), and Error(message) variants lets a when expression over it be exhaustive — the compiler flags a missing branch — so a new state variant can't silently fall through unhandled in the UI.

Advanced Deep Dives

How does Compose decide what to recompose, and how do you avoid unnecessary recompositions?

Compose tracks which composables read which State objects during composition and only re-invokes the composables that actually read a value that changed ("smart recomposition"), skipping the rest of the tree. To avoid unnecessary work: keep state as close as possible to where it's used (avoid hoisting further than needed), use stable/immutable data classes so Compose can skip recomposition when nothing meaningfully changed, and use derivedStateOf for values computed from other state so they only trigger recomposition when the derived result actually changes.

What is SharingStarted.WhileSubscribed(timeout) used for with stateIn?

It controls when a hot StateFlow created from a cold Flow (e.g. a Room query) starts and stops collecting the upstream. WhileSubscribed(5000) keeps the upstream active for 5 seconds after the last collector unsubscribes — long enough to survive a quick configuration change like rotation without restarting the query, but short enough to release resources when the screen is truly gone.

How does Hilt's dependency graph work, and what does @Singleton actually scope to?

Hilt generates a hierarchy of components mirroring Android's lifecycle owners (Application, Activity, Fragment, ViewModel, etc.). @Singleton scopes a binding to the SingletonComponent, tied to the Application's lifecycle — one instance for the entire app process, created lazily on first injection and never destroyed until the process dies.

What's the difference between Dispatchers.IO and Dispatchers.Default?

Dispatchers.IO is backed by a large, elastic thread pool optimized for blocking I/O work (network calls, disk access, database queries) where threads spend most of their time waiting. Dispatchers.Default is backed by a thread pool sized to the number of CPU cores, optimized for CPU-intensive work like sorting large lists or parsing. Using the wrong one — e.g. CPU-heavy work on IO — can starve other I/O operations or waste threads.

How would you handle process death with unsaved user input in a form screen?

Use SavedStateHandle inside the ViewModel to persist critical form fields — it survives process death (unlike plain ViewModel state, which only survives configuration changes) by writing to the same Bundle the system already saves for instance state. Read/write through savedStateHandle.get/set or expose it as a StateFlow so the UI updates automatically when state is restored after the process is recreated.

What's the difference between @Stable and @Immutable in Compose?

Both are promises to the compiler that unlock skipping recomposition, but they differ in strength. @Immutable promises every public property is permanently fixed after construction. @Stable is weaker: properties may change, but whenever one does, Compose is notified (e.g. via MutableState) so it can react — equal instances of a stable type also always produce equal results from its functions. A plain class holding a var that the compiler can't infer stability for is treated as unstable by default, which disables the recomposition-skipping optimization for composables that read it.

How does WorkManager guarantee a task eventually runs, even across app kills and device reboots?

WorkManager persists queued work requests to an internal Room database rather than keeping them only in memory, so the schedule survives process death. It delegates actual execution to JobScheduler, AlarmManager, or a foreground service depending on API level and constraints, and re-registers pending work with the system on boot via a BOOT_COMPLETED receiver — which is why apps using it need the RECEIVE_BOOT_COMPLETED permission.

Keep practicing

Take the Android practice quiz or walk through a full project tutorial.

Practice Quiz