{"id":1146,"date":"2026-07-22T09:24:00","date_gmt":"2026-07-22T03:54:00","guid":{"rendered":"https:\/\/lastroundai.com\/blog\/?post_type=iq&#038;p=1146"},"modified":"2026-07-21T22:32:44","modified_gmt":"2026-07-21T17:02:44","slug":"android-developer","status":"publish","type":"iq","link":"https:\/\/lastroundai.com\/interview-questions\/android-developer","title":{"rendered":"Android Developer Interview Questions (2026): Most Asked Q&#038;A (Kotlin)"},"content":{"rendered":"<p>Google&#8217;s own developer documentation says it plainly: &#8220;Jetpack Compose is Android&#8217;s recommended modern toolkit for building native UI.&#8221; That&#8217;s not a slide from a keynote, it&#8217;s the current guidance every serious Android team reads, and it has quietly rewritten what a 2026 interview loop actually tests.<\/p>\n<p>I think most &#8220;Android interview questions&#8221; lists still circulating online are stuck around 2019, XML layouts, AsyncTask, maybe an RxJava chain nobody ships anymore. That&#8217;s a real gap. A senior Android loop today expects you to reason about coroutines, StateFlow, and recomposition, on top of everything that hasn&#8217;t changed in a decade: lifecycle behavior, memory pressure, background work constraints. Get the newer half wrong and you look out of date. Get the older half wrong and you look junior, no matter how polished your Compose demo is.<\/p>\n<p>This page covers the Android interview questions that actually show up in loops right now, Kotlin fundamentals through Compose, architecture, and the background-work plumbing that never makes it into a portfolio README. Every question below includes what the interviewer is actually testing for, not just the textbook answer.<\/p>\n<div class=\"iq-stats not-prose\"><div class=\"iq-stat\"><span class=\"iq-stat__value\">Kotlin<\/span><span class=\"iq-stat__label\">Core language<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">Jetpack Compose<\/span><span class=\"iq-stat__label\">UI toolkit<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">3-5 rounds<\/span><span class=\"iq-stat__label\">Typical loop<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">Easy-Medium<\/span><span class=\"iq-stat__label\">Coding level<\/span><\/div><\/div>\n<h2>Kotlin fundamentals interviewers actually probe<\/h2>\n<p>Kotlin isn&#8217;t new anymore, Google made it the preferred language for Android back in 2019, so interviewers assume fluency, not familiarity. Where they actually spend time is null safety and coroutines, since that&#8217;s where sloppy real-world code shows up.<\/p>\n<div class=\"iq-dsec iq-dsec--easy\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"easy\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Easy questions<\/h2><span class=\"iq-dsec__n\">15<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How does Kotlin&#039;s null safety system actually work, and what&#039;s wrong with using !! everywhere?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Kotlin<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Kotlin distinguishes nullable types (String?) from non-null types (String) at compile time. The safe call operator ?. returns null instead of crashing if the receiver is null, and the Elvis operator ?: supplies a fallback value. The double-bang !! operator forces a null check and throws a NullPointerException if the value actually is null, it exists mainly for interop with Java code that doesn&#8217;t carry nullability information.<\/p>\n<p>Interviewers push on this because !! shows up constantly in rushed code, and it defeats the entire point of Kotlin&#8217;s null safety. A good answer names a real alternative, ?.let { }, ?:, or restructuring the function to take a non-null parameter instead of asserting one exists.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the actual difference between let and apply, and when do you reach for one over the other?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Kotlin<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Both are scope functions, but let passes the receiver in as it and returns the lambda&#8217;s result, while apply passes the receiver as this and always returns the receiver itself. Use let when you want to transform a value or run null-safe logic on it (user?.let { sendEmail(it) }). Use apply when you&#8217;re configuring an object&#8217;s properties and want the object back at the end, like builder-style setup.<\/p>\n<p>Candidates who&#8217;ve only memorized definitions usually can&#8217;t explain why you&#8217;d pick one over the other in a real snippet. Interviewers ask for the return-value difference specifically because that&#8217;s what actually breaks code when you swap them.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Walk through the Activity lifecycle callbacks in order.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Lifecycle<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>onCreate() runs once when the Activity is first created, followed by onStart() (visible but not interactive) and onResume() (in the foreground, receiving input). Going backward: onPause() fires when another Activity partially covers this one, onStop() when it&#8217;s no longer visible, and onDestroy() when the system reclaims it or the user finishes it. Coming back from the background re-triggers onRestart() then onStart() again.<\/p>\n<p>One detail trips people up, onSaveInstanceState() isn&#8217;t guaranteed to fire before onStop() on every API level. As of Android 9 (API 28), the platform explicitly calls it after onStop(), on older versions the order was less strict. I&#8217;ve seen sample code online that assumes the old ordering and breaks in subtle ways on newer devices, so don&#8217;t just memorize the diagram, check which API level the guarantee actually applies to.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What problem does ViewModel actually solve, and what&#039;s off-limits to store in it?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Jetpack<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>ViewModel holds UI-related state and survives configuration changes, so a rotation doesn&#8217;t force you to re-fetch data or lose in-progress state. It should never hold a reference to a View, Fragment, or Activity Context, doing so leaks the entire view hierarchy for as long as the ViewModel lives, which can be longer than the Activity itself.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What are Entity, DAO, and Database in Room, and what happens if you query the database from the main thread?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Jetpack<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>An @Entity maps a class to a table, a @Dao interface declares your queries, and the abstract @Database class ties entities and DAOs together. Room throws an IllegalStateException if you run a query on the main thread by default, DAO methods are expected to be suspend functions or return a Flow or LiveData so the query naturally happens off the UI thread. You can disable this check with allowMainThreadQueries(), but doing so in real code is close to always the wrong call.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why put a repository between your ViewModel and your data sources instead of calling Room or Retrofit directly?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Architecture<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A repository gives you one place that decides where data comes from, cache, database, or network, and coordinates between them: serve from Room, refresh from network in the background, update Room, let the UI observe the single source of truth. Without it, that decision logic ends up duplicated across every ViewModel that needs the same data, and testing means mocking Retrofit and Room instead of one clean interface.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why does RecyclerView need the ViewHolder pattern, and what breaks if you skip it?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Lists<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>ViewHolder caches the result of findViewById() for each row so scrolling doesn&#8217;t repeatedly walk the view hierarchy looking up the same references. Skip it, or implement it wrong, and every scroll event triggers unnecessary view lookups and inflation, which shows up as visible jank on anything but a flagship device.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between an explicit and an implicit Intent?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Intents<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>An explicit Intent names the exact component to start, Intent(this, DetailActivity::class.java), used for navigation within your own app. An implicit Intent describes an action to perform, Intent(Intent.ACTION_VIEW, uri), and lets the system pick whichever installed app can handle it, sharing a link, opening a map location, sending an email.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Give an example of a WorkManager constraint, and explain what it actually does.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Background Work<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>setRequiredNetworkType(NetworkType.UNMETERED) tells WorkManager not to run the work until the device is on Wi-Fi, and requiresCharging(true) delays it until the device is plugged in. These aren&#8217;t checked once at scheduling time, WorkManager re-evaluates constraints continuously and only actually runs the work once all of them are satisfied, pausing it again if a constraint stops holding mid-execution.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Do Android interviews still ask about XML layouts and Views, or is it all Compose now?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">FAQ<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Most 2026 loops lead with Compose since it&#8217;s Google&#8217;s recommended toolkit, but the View system isn&#8217;t gone. Plenty of production codebases are mid-migration, so expect at least a light question about how the two interoperate, ComposeView, AndroidView, if the company&#8217;s app predates Compose.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Is Java still acceptable in Android coding rounds, or do I need Kotlin specifically?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">FAQ<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Kotlin is the default expectation for almost every Android role advertised in 2026. A handful of large legacy codebases still interview in Java for maintenance-heavy roles, but if the job description doesn&#8217;t say otherwise, prepare in Kotlin.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Do companies ask LeetCode-style algorithm questions in Android interviews?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">FAQ<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Yes, typically at Easy to Medium difficulty, arrays, hash maps, basic graph or tree problems. It&#8217;s usually one round out of several, not the entire loop, unlike a pure backend SWE interview where DSA rounds can dominate.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Should I learn Kotlin Multiplatform (KMP) before an Android interview?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">FAQ<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Not required for most roles in 2026, but it&#8217;s worth mentioning if you&#8217;ve used it. Some teams are actively evaluating or migrating shared business logic to KMP, and a candidate who understands the trade-offs stands out. Don&#8217;t over-invest prep time in it unless the job description specifically mentions cross-platform work.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between an APK and an Android App Bundle (AAB), and why does the Play Store require AAB now?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">APK vs AAB<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>An APK is the single installable package, and it has to carry every resource density and native library ABI your app supports because you don&#8217;t know ahead of time which device it lands on, which is why a universal APK ends up so much bigger than it needs to be for any one phone. An AAB isn&#8217;t installable on its own, it&#8217;s a publishing format you upload to Play Console, and Google&#8217;s servers split it into device-specific APKs at install time through dynamic delivery, so a phone with an arm64 chip and an xxhdpi screen only downloads the slice it actually needs instead of every density and architecture combination bundled together.<\/p>\n<p>Google made AAB mandatory for new apps mainly because it cuts average install size meaningfully and unlocks Play Feature Delivery for on-demand modules and Play Asset Delivery for large game assets. Day to day, you still build and run APKs locally in Android Studio, and you can produce a universal APK from an AAB with bundletool for sideloading or testing, but the artifact you actually ship to the Store is the AAB.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What are the four core Android application components, and what&#039;s each one used for?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">App Components<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Activities, Services, BroadcastReceivers, and ContentProviders are the four component types the OS treats as first-class citizens. Each one has to be declared (in the manifest, or registered dynamically for a receiver) and each gets its own lifecycle the system manages, rather than your code just instantiating a plain object whenever it wants one.<\/p>\n<p>An Activity is a single screen with a UI the user interacts with directly. A Service does work without a UI, either bound, where a client connects and calls methods on it directly like controlling a music player from a notification, or started, where it runs fire-and-forget until it stops itself. A BroadcastReceiver reacts to system-wide or app-wide events such as connectivity changes, boot completion, or a custom broadcast your own app sends. A ContentProvider exposes structured, queryable data to other apps through a standard interface, the way the system Contacts provider does, backed by Binder IPC under the hood.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-dsec iq-dsec--medium\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"medium\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Medium questions<\/h2><span class=\"iq-dsec__n\">25<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between launch and async in Kotlin coroutines?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Coroutines<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>launch starts a coroutine and returns a Job, used when you don&#8217;t need a result back, fire it and move on. async returns a Deferred, and you call .await() to get the result, used when you need a value from concurrent work, like two parallel network calls you&#8217;ll combine later.<\/p>\n<p>The exception-handling nuance is where this gets interesting. Inside a regular coroutineScope, an unhandled exception from either builder still cancels the whole scope immediately, structured concurrency doesn&#8217;t wait around. The practical difference is that async&#8217;s exception won&#8217;t surface to your code until you call await(), which means a forgotten await() can silently swallow a crash. I&#8217;ve seen this exact bug in production code more than once.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What are Kotlin coroutine dispatchers, and how do you pick one?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Coroutines<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Dispatchers.Main runs on the UI thread, for anything touching views. Dispatchers.IO is a larger thread pool tuned for blocking work like disk access or network calls. Dispatchers.Default is tuned for CPU-heavy work like sorting a big list or parsing JSON. viewModelScope, by the way, defaults to Dispatchers.Main.immediate, not IO, which trips people up when they assume ViewModel coroutines are automatically off the main thread.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">kotlin<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-kotlin\">\n\nclass ArticleRepository(private val api: ArticleApi) {\n    suspend fun fetchArticle(id: String): Article = withContext(Dispatchers.IO) {\n\n        val response = api.getArticle(id)\n\n        response.toDomainModel()\n\n    }\n\n}\n<\/code><\/pre><\/div><\/p>\n<p>The withContext(Dispatchers.IO) pattern is what interviewers want to see in the repository layer, not sprinkled through the ViewModel. If your answer puts dispatcher-switching logic inside the UI layer, that&#8217;s usually a sign you haven&#8217;t separated concerns cleanly.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What happens to your Activity on a configuration change like a screen rotation, and why doesn&#039;t ViewModel alone fix everything?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Lifecycle<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>By default, a configuration change destroys and recreates the Activity, which is why fields on the Activity itself get wiped. ViewModel survives this because it lives in a ViewModelStore that the framework keeps alive across the recreation, but it does not survive process death, when Android kills your backgrounded app under memory pressure.<\/p>\n<p>That&#8217;s what SavedStateHandle is for. It&#8217;s backed by the same bundle mechanism as onSaveInstanceState(), restored even after process death, subject to the same size limits (large objects will throw a TransactionTooLargeException if you&#8217;re not careful). Most teams test rotation constantly and process death almost never, which is a real gap, that bug only shows up in production when a user&#8217;s phone is under memory pressure and QA never reproduces it.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">LiveData vs. StateFlow, which one would you pick starting a new screen in 2026?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Jetpack<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>LiveData is lifecycle-aware automatically and stops delivering updates when the observer&#8217;s lifecycle isn&#8217;t active, which made it the safe default for years. StateFlow needs repeatOnLifecycle or collectAsStateWithLifecycle() in Compose to get the same safety, it&#8217;s not automatic. In exchange, StateFlow is plain Kotlin, testable outside Android, usable in shared or multiplatform code, and it composes naturally with the rest of the coroutines ecosystem.<\/p>\n<p>If I&#8217;m starting something today, I don&#8217;t see a strong reason to reach for LiveData over StateFlow. That said, plenty of production codebases have years of LiveData already working correctly, and rewriting something that already works just to chase a trend is a waste of a sprint. Expect interviewers to probe which side of that trade-off you actually understand, not just which one you&#8217;d pick.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How does the Navigation component pass data between destinations safely?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Jetpack<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Safe Args, a Gradle plugin, generates typed Directions and Args classes from your navigation graph, so arguments are compile-time checked instead of stuffed into a raw Bundle with string keys. The NavController manages a single back stack tied to a NavHost, and popUpTo with inclusive=&#8221;true&#8221; is how you clear screens off that stack, the standard example being clearing the entire login flow once the user reaches the home screen, so back doesn&#8217;t return them to the login form.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What does it mean that Compose is declarative, and what actually triggers recomposition?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Compose<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Instead of imperatively mutating a View tree, findViewById then setting .text, you describe what the UI should look like for a given state, and Compose figures out what to redraw. Recomposition happens when a State object being read inside a composable changes value. Compose tracks which composables read which state during the last composition and only re-runs the ones that actually depend on what changed, not the whole tree.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between remember and rememberSaveable?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Compose<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>remember keeps a value alive across recompositions, but it&#8217;s lost when the Activity is recreated, a configuration change, because the whole composition is thrown away and rebuilt. rememberSaveable survives that by saving the value into the same bundle mechanism onSaveInstanceState() uses, with the same size and type constraints. It works out of the box for primitives and Parcelables, anything else needs a custom Saver.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">kotlin<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-kotlin\">\n\n@Composable\n\nfun CounterButton() {\n\n    var count by rememberSaveable { mutableStateOf(0) }\n    Button(onClick = { count++ }) {\n\n        Text(\u201cClicked $count times\u201d)\n\n    }\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s each layer&#039;s job in MVVM, and what&#039;s the most common mistake teams make implementing it?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Architecture<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The View, an Activity, Fragment, or composable, renders state and forwards user actions. The ViewModel holds and transforms UI state, exposed as LiveData or StateFlow, without knowing anything about how it&#8217;s rendered. The Model is your data layer, repositories, Room, network. The most common mistake: ViewModels that end up holding a Context, a View reference, or navigation logic directly, which breaks testability and re-introduces the exact leak risk MVVM was supposed to prevent.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What does DiffUtil actually calculate, and why is ListAdapter usually better than calling notifyDataSetChanged()?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Lists<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>DiffUtil compares an old list and a new list and computes the minimal set of insert, remove, and move operations needed to turn one into the other, using an algorithm based on Eugene Myers&#8217;s diff paper. notifyDataSetChanged() throws that away and just rebinds every visible row, which loses scroll position context and kills any row-level animation. ListAdapter wraps DiffUtil in AsyncListDiffer, which runs the diff calculation on a background thread automatically, so you get the smarter updates without blocking the UI thread on a large list.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">kotlin<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-kotlin\">\n\nclass ArticleDiffCallback : DiffUtil.ItemCallback&lt;Article&gt;() {\n    override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean {\n\n        return oldItem.id == newItem.id\n\n    }\n    override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean {\n\n        return oldItem == newItem\n\n    }\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How does Compose&#039;s LazyColumn compare to RecyclerView for a long scrolling list?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Lists<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Conceptually similar, items outside the visible viewport aren&#8217;t composed, and LazyColumn reuses layout nodes for items that share the same content type as you scroll, the same recycling idea RecyclerView popularized, just handled for you. The part people miss is the key parameter, without a stable key tied to item identity, not just index, reordering a list can scramble per-item state and break animations, the same class of bug DiffUtil&#8217;s areItemsTheSame exists to prevent.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">WorkManager vs. a coroutine in viewModelScope, how do you decide?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Background Work<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>viewModelScope is for work that only matters while the associated screen or process is alive, it&#8217;s cancelled automatically when the ViewModel is cleared. WorkManager is for work that has to happen even if the app is killed, the process dies, or the device reboots, a queued photo upload, a periodic sync, anything the user would be upset to silently lose. Under the hood WorkManager picks between JobScheduler, AlarmManager with a BroadcastReceiver, or a foreground service depending on the API level and constraints, so you don&#8217;t have to.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why did Google recommend moving away from SharedPreferences toward Jetpack DataStore?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Storage<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>SharedPreferences reads the entire file into memory on first access and has no fully asynchronous, transactional API. apply() is non-blocking for the caller but still funnels through a single lock, and there&#8217;s no built-in error signaling if a write silently fails. DataStore, Preferences or Proto, exposes reads as a Flow, handles writes transactionally on Dispatchers.IO, and with Proto DataStore gives you actual type safety instead of loosely-typed key-value pairs.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How much system design should I expect for a mid-level Android role?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">FAQ<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Less than a backend system design round, usually. Expect app-level design questions instead, offline-first sync strategy, how you&#8217;d structure a feature module, how you&#8217;d handle a flaky network, not distributed systems questions about sharding a database across regions.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the single most common reason strong Android candidates fail the interview?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">FAQ<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Being able to build features but not explain the reasoning behind lifecycle or memory-related decisions under a follow-up question. Interviewers use the follow-up specifically to separate memorized answers from real understanding, and it&#8217;s where otherwise strong candidates lose the round.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What happens to a Fragment&#039;s view when it goes on the back stack, and why does forgetting to null out a View Binding reference in onDestroyView cause a real leak?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Fragment View Binding<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A Fragment&#8217;s view hierarchy is destroyed and recreated independently of the Fragment instance itself. When a Fragment gets pushed onto the back stack, or its view otherwise goes away, onDestroyView() fires and the root view plus everything under it gets torn down, but the Fragment object stays alive until onDestroy(), which might not happen for a long time if the user just keeps navigating forward.<\/p>\n<p>If you hold your View Binding in a class-level property and never clear it in onDestroyView, you&#8217;re keeping the whole destroyed view hierarchy reachable from a Fragment that&#8217;s still alive on the back stack, which is a genuine memory leak, not just a theoretical one. The standard fix is a nullable backing property that gets set in onCreateView and cleared in onDestroyView, so you only ever touch the binding between those two callbacks and get an obvious null pointer instead of a silent leak if you mess it up.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do two sibling Fragments in the same Activity share data without talking to each other directly?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Shared ViewModel<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The standard pattern is a ViewModel scoped to the shared Activity, requested through activityViewModels() instead of the default viewModels(), so both fragments get handed the same instance because they&#8217;re both keying off the Activity&#8217;s ViewModelStore rather than getting their own. A StateFlow or LiveData exposed from that shared ViewModel becomes the single source of truth both fragments read from and mutate through its methods, with no direct reference between the fragments at all.<\/p>\n<p>This replaces the older pattern of a Fragment calling a method on its host Activity through an interface the Activity implements, which worked but tightly coupled the Fragment to whatever Activity happened to host it and made it harder to test the Fragment in isolation. One thing to watch for: activityViewModels() means that state outlives any single Fragment and is visible to every fragment in that Activity&#8217;s stack, not just the two you intended to talk to each other, so it&#8217;s the wrong choice if what you actually need is fragment-local state.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why do teams model network or UI state with a Kotlin sealed class instead of an isLoading boolean plus a nullable data field?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Sealed Classes<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A sealed class or sealed interface like Result.Loading, Result.Success(data), Result.Error(exception) makes illegal states unrepresentable, and the compiler forces every `when` block over it to handle each branch, refusing to compile if you add a new state later and forget to update a consumer. With a boolean flag plus nullable fields instead, nothing stops combinations that don&#8217;t make sense, isLoading true while data is also non-null, or both error and data populated at once, and you just get subtly wrong UI at runtime with no compiler warning.<\/p>\n<p>Sealed classes also let each state carry only what&#8217;s relevant to it, Success carries the payload, Error carries the throwable and maybe a retry count, instead of one flat object with a pile of optional fields that are null most of the time. The tradeoff is a bit more boilerplate up front, and you only get the compiler&#8217;s exhaustiveness check if you skip adding an `else` branch out of habit, since `else` quietly defeats the whole point.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What does Kotlin generate for a data class, and where does that generated equals() actually bite people?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Data Class Equals<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>For every property in the primary constructor, the compiler generates equals(), hashCode(), toString(), copy(), and componentN() functions for destructuring. The generated equals() only looks at primary-constructor properties, so a var declared in the class body instead of the constructor is invisible to it, meaning two instances the code treats as equal can silently differ on that field, and code depending on equality, like DiffUtil or a Set, won&#8217;t notice the difference.<\/p>\n<p>The other trap is mutable collections inside a data class used as a set element or map key. Mutating that list after the object&#8217;s been inserted breaks the invariant that hashCode stays stable for an object while it&#8217;s stored, since the object no longer hashes to the bucket it&#8217;s actually sitting in, and `contains()` can return false for something clearly in the set.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">kotlin<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-kotlin\">\n\ndata class Item(val id: String) {\n\n    var isSelected: Boolean = false \/\/ not in the primary constructor\n\n}\nval a = Item(\u201c1\u201d).apply { isSelected = true }\n\nval b = Item(\u201c1\u201d).apply { isSelected = false }\n\na == b \/\/ true, equals() never looks at isSelected\n<\/code><\/pre><\/div><\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Flow and LiveData both let you observe a stream of values. What&#039;s actually different between them under the hood?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Flow vs LiveData<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>LiveData is lifecycle-aware by construction, it only notifies observers when the LifecycleOwner is at least STARTED and unsubscribes automatically on destroy, and it&#8217;s tied to Android and the main thread, so it doesn&#8217;t work in plain Kotlin modules or shared code without extra glue. Flow is a general-purpose coroutines construct, cold by default, meaning the producer block doesn&#8217;t run until something collects it and each new collector re-runs it from scratch unless you explicitly share it, and it has zero lifecycle awareness baked in.<\/p>\n<p>You get lifecycle awareness back with Flow by collecting inside `repeatOnLifecycle(Lifecycle.State.STARTED)` in a lifecycleScope.launch, which mirrors what LiveData did for free. What Flow actually buys you is the operator set, map, debounce, distinctUntilChanged, flatMapLatest, combine, which composes cleanly for something like search-as-you-type, where the LiveData equivalent means dropping into Transformations.switchMap and losing most of that expressiveness. Most new screens skip LiveData in the ViewModel layer entirely now and only reach for it to bridge into older Java call sites that expect it.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the practical difference between StateFlow and SharedFlow, and when would you actually reach for SharedFlow?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">StateFlow vs SharedFlow<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>StateFlow always holds a current value, requires an initial value up front, conflates emissions so a slow collector only ever sees the latest state rather than every intermediate one, and always replays that latest value to any new collector. That&#8217;s exactly the behavior you want for &#8220;the current state of this screen.&#8221; SharedFlow has no required initial value and no built-in conflation, you configure replay count and buffer behavior yourself, and with the default replay of zero, a new collector only sees emissions that happen after it starts collecting.<\/p>\n<p>That makes SharedFlow the right tool for one-shot events, show this snackbar, navigate to this screen, things you don&#8217;t want replayed to every future subscriber. A recurring bug is using StateFlow for a one-off navigation event, then discovering navigation refires on configuration changes because StateFlow replays its last value to every new collector, exactly the opposite of what a single-shot event needs.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">kotlin<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-kotlin\">\n\nprivate val _uiState = MutableStateFlow(UiState.Loading) \/\/ always has a value, replays latest\n\nval uiState: StateFlow&lt;UiState&gt; = _uiState\nprivate val _events = MutableSharedFlow&lt;UiEvent&gt;() \/\/ no replay by default\n\nval events: SharedFlow&lt;UiEvent&gt; = _events\n<\/code><\/pre><\/div><\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What are the most common causes of memory leaks in an Android app, and how does something like LeakCanary actually catch them?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Memory Leaks<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The classic causes are all versions of a long-lived object holding a reference to something that should be short-lived: a static field or singleton holding onto an Activity or View context, an inner non-static class or anonymous listener implicitly capturing its outer Activity after that Activity is destroyed, a Handler created without an explicit Looper holding a reference to the enclosing Activity through its posted Runnables, and registered listeners like a BroadcastReceiver or a location callback that never get unregistered in onStop or onDestroy.<\/p>\n<p>LeakCanary works by hooking into onDestroy and onDestroyView, watching those objects with a weak reference, forcing a GC, then checking a short while later whether the reference is still alive. If it is, that object should have been collected and wasn&#8217;t, so LeakCanary walks the heap dump and computes the shortest path from a GC root to that object, which is exactly the leak trace it shows you, pointing at the field that&#8217;s holding on. In practice the fix is almost always one of three moves: use a weak reference for anything long-lived pointing at something Android will destroy, unregister listeners in the matching lifecycle callback, and use applicationContext instead of an Activity context for anything meant to outlive the Activity.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What problem does Hilt actually solve compared to just constructing your dependencies by hand or passing them through constructors?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Hilt DI<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Manual dependency injection still works at small scale, you write factory functions or a hand-built container of singletons, but it stops scaling once every class needs to know how to construct everything it depends on, and getting the lifetime right per Activity, Fragment, or ViewModel by hand is easy to get wrong, letting a singleton-scoped object leak into something shorter-lived or the reverse.<\/p>\n<p>Hilt generates that container code at compile time from annotations. You mark a constructor @Inject, scope the class as @Singleton, @ActivityScoped, or @ViewModelScoped depending on how long it should live, and Hilt wires up the graph while enforcing those scope rules, throwing a compile error if you try to inject something Activity-scoped into a class that outlives the Activity. It&#8217;s built on Dagger, so you get compile-time safety with no runtime reflection, without hand-writing Dagger&#8217;s components and modules yourself. The real cost is build time, since annotation processing adds real minutes to a clean build on a large app, which is why some teams keep small modules on manual DI or Koin instead.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">kotlin<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-kotlin\">\n\n@Singleton\n\nclass UserRepository @Inject constructor(\n\n    private val api: UserApi,\n\n    private val db: UserDao\n\n)\n<\/code><\/pre><\/div><\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What actually triggers an ANR, and how do you debug one that never reproduces locally?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">ANR Debugging<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>An ANR fires when the main thread doesn&#8217;t respond to an input event within five seconds, or a BroadcastReceiver&#8217;s onReceive doesn&#8217;t return within ten seconds, and the system shows the app-isn&#8217;t-responding dialog while generating a traces file with every thread&#8217;s stack at that moment. Locally, StrictMode is the first line of defense, flagging disk reads, writes, and network calls happening on the main thread as they occur in a debug build, which catches a large share of ANR causes before they ever reach production.<\/p>\n<p>For an ANR you can&#8217;t reproduce, Play Console&#8217;s Android vitals gives you the actual stack collected from real devices, and the first thing to check is what the main thread was doing at the top of that trace, a synchronous DB query, a lock wait, a synchronous binder call into another process. A less obvious cause worth checking is contention on a lock held by a background thread the main thread is also waiting on, which won&#8217;t look like &#8220;the main thread did something slow&#8221; in a naive read, so you have to check what every thread is doing, not just the main one, since the main thread can look idle while it&#8217;s actually blocked on a mutex.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why does a ViewModel&#039;s data survive a screen rotation but not the process being killed by the system, and what&#039;s SavedStateHandle for?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">SavedStateHandle<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A configuration change destroys and recreates the Activity, but the system deliberately keeps the ViewModelStore alive across that specific cycle because it knows the change is coming, so the new Activity gets handed back the same ViewModel instance. Process death is different, the OS kills the whole process, ViewModel included, to reclaim memory, which can happen to any backgrounded app, and when the user navigates back Android spins up a brand new process and calls onCreate with a saved Bundle, but there&#8217;s no ViewModel object to hand back since the in-memory instance is gone along with everything else.<\/p>\n<p>SavedStateHandle bridges that gap. It&#8217;s a small key-value store injected into the ViewModel automatically that gets saved into the same Bundle used before process death and restored into a fresh SavedStateHandle when the process and ViewModel get recreated, so the ViewModel checks that handle for saved values instead of assuming a clean start. The catch is that values have to be small and parcelable since they go through the same Bundle mechanism as onSaveInstanceState, so it&#8217;s for &#8220;which item is selected&#8221; or &#8220;what&#8217;s in the search box,&#8221; not for caching a large list of network results.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What problem does the Paging 3 library solve that you can&#039;t easily solve by just calling loadMore() on scroll yourself?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Paging 3<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Hand-rolled pagination usually means manually tracking a page or offset cursor, deciding when the RecyclerView is close enough to the bottom to trigger the next request, deduplicating requests if fast scrolling triggers two loads before the first one returns, and handling error and retry state per page, and most homegrown implementations get at least one of those wrong under fast scrolling or rotation.<\/p>\n<p>Paging 3 handles all of it through a PagingSource that knows how to load a page given a key, a Pager that turns a stream of those into a PagingData Flow, and a PagingDataAdapter that diffs incoming pages against what&#8217;s already shown, so loads trigger automatically at a configurable prefetch distance without duplicate in-flight requests. You also get separate loading and error states per load type, refresh, prepend, append, that you can surface directly in the UI, like a retry footer if the next page fails, and if you need local cache plus network, a RemoteMediator plugs into the same Pager so Room stays the actual source of truth while network results populate it in the background.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-dsec iq-dsec--hard\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"hard\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Hard questions<\/h2><span class=\"iq-dsec__n\">12<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why does observing LiveData with a Fragment&#039;s own lifecycleOwner instead of viewLifecycleOwner cause bugs?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Lifecycle<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A Fragment has two separate lifecycles: the Fragment instance&#8217;s own lifecycle, and the View lifecycle, tracked by getViewLifecycleOwner(), which is shorter. It&#8217;s destroyed and recreated independently when the Fragment is added to the back stack, even though the Fragment instance itself stays alive.<\/p>\n<p>If you observe LiveData using the Fragment&#8217;s lifecycle instead of the view&#8217;s, the observer keeps firing after the view is destroyed, either crashing on a null view reference or silently leaking the old view. This is a classic case where the &#8220;obviously correct&#8221; answer, just use this as the LifecycleOwner, is wrong in a way that only shows up when the Fragment goes through the back stack, not on first load.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">When do you use LaunchedEffect versus rememberCoroutineScope?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Compose<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>LaunchedEffect(key) ties a coroutine to the composition itself, it starts when the composable enters composition and restarts automatically whenever its key changes, good for reacting to state, like fetching data when a screen ID changes. rememberCoroutineScope gives you a CoroutineScope tied to the composable&#8217;s lifecycle that you control manually, used when you need to launch a coroutine from an event callback, a button&#8217;s onClick, since you can&#8217;t call suspend functions directly from a non-suspend lambda.<\/p>\n<p>The follow-up interviewers ask: &#8220;what happens if you put LaunchedEffect inside the onClick lambda instead?&#8221; You can&#8217;t, LaunchedEffect is a composable function itself and can only be called during composition, not from an arbitrary callback. That distinction alone weeds out people who&#8217;ve copied Compose code without understanding it.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the size limit gotcha when passing data between screens with Intent extras?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Intents<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Intent extras go through Binder, and the whole transaction buffer is shared across everything happening in your process at that moment, commonly cited around 1MB total, not 1MB per Intent. Pass a large Bitmap, a big Parcelable list, or several screens&#8217; worth of extras in quick succession, and you can hit TransactionTooLargeException well before any single object looks obviously too big. The fix is usually to hold large data in a shared ViewModel, a repository, or an in-memory cache, and pass a lightweight ID or key through the Intent instead of the object itself.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Implement your own LRU cache, similar to what Android&#039;s built-in LruCache class does internally, for caching decoded bitmaps in memory.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Coding Question<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The standard approach: a LinkedHashMap constructed with accessOrder = true, so the map itself keeps track of recency order for you, then override the size check to evict the oldest entry once you&#8217;re over a size budget you track manually. Android&#8217;s own LruCache does this conceptually, tracking a running total in bytes rather than pure entry count, since bitmaps of different sizes shouldn&#8217;t count as one unit each.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">kotlin<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-kotlin\">\n\nclass BitmapLruCache(private val maxSizeBytes: Int) {\n\n    private var currentSize = 0\n\n    private val cache = object : LinkedHashMap&lt;String, Bitmap&gt;(16, 0.75f, true) {\n\n        override fun removeEldestEntry(eldest: MutableMap.MutableEntry&lt;String, Bitmap&gt;): Boolean {\n\n            if (currentSize &gt; maxSizeBytes) {\n\n                currentSize -= sizeOf(eldest.value)\n\n                return true\n\n            }\n\n            return false\n\n        }\n\n    }\n    @Synchronized\n\n    fun put(key: String, bitmap: Bitmap) {\n\n        cache[key] = bitmap\n\n        currentSize += sizeOf(bitmap)\n\n    }\n    @Synchronized\n\n    fun get(key: String): Bitmap? = cache[key]\n    private fun sizeOf(bitmap: Bitmap): Int = bitmap.byteCount\n\n}\n<\/code><\/pre><\/div><\/p>\n<p>Interviewers follow up by asking what happens under concurrent access from multiple threads. The @Synchronized annotation above is the minimum bar, but a real production version usually needs a proper lock strategy if put and get are called from background threads at high frequency, not just the UI thread.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How does the Android main thread actually process events, and how would you run repeated background work without spinning up a new Thread every time?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Looper Handler<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Any thread that wants to process a queue of messages needs a Looper and a MessageQueue. The main thread gets one automatically through Looper.prepareMainLooper() before your app&#8217;s code even runs, and from then on it&#8217;s just a loop pulling Message objects off that queue one at a time and dispatching each to whatever Handler posted it. Every &#8220;run this on the main thread&#8221; mechanism in Android, View.post, Handler.post, Dispatchers.Main, ultimately funnels through posting a Message onto that same queue, which is also why one expensive callback blocks everything else waiting behind it, there&#8217;s no preemption, whatever&#8217;s running finishes before the next message gets pulled.<\/p>\n<p>For background work that needs its own serial queue, sequential database writes off the UI thread without spinning up a Thread per write, a HandlerThread gives you a real Thread with Looper.prepare() already called, and after it starts you grab its Looper and build a Handler pointed at it. Every Runnable posted to that Handler executes serially on that one thread in arrival order, with no manual queue and no risk of two writes racing each other. In coroutine terms this maps onto a single-threaded dispatcher, which is exactly why coroutines don&#8217;t force you to think about Looper and Handler explicitly anymore, even though the mechanism underneath hasn&#8217;t changed at all.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">kotlin<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-kotlin\">\n\nval handlerThread = HandlerThread(\u201cdb-writer\u201d).apply { start() }\n\nval handler = Handler(handlerThread.looper)\nhandler.post {\n\n    \/\/ runs serially on its own thread, one write at a time\n\n    database.insert(record)\n\n}\n<\/code><\/pre><\/div><\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Walk through what happens when a user touches a nested view inside a ViewGroup, and where the &#039;my child view never gets the touch&#039; bug usually comes from.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Touch Event Dispatch<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Touch dispatch starts at the Activity and calls dispatchTouchEvent on the root ViewGroup, which calls its own onInterceptTouchEvent before asking any child; if that returns false, the event gets forwarded to whichever child&#8217;s bounds contain the touch point, and this repeats recursively down to a leaf View, which finally decides in its own onTouchEvent whether to consume it. Once a View returns true for ACTION_DOWN, it keeps receiving every subsequent event in that gesture, ACTION_MOVE, ACTION_UP, even if the finger moves outside its bounds, because Android tracks gesture ownership from the DOWN event rather than re-hit-testing on every move.<\/p>\n<p>The common bug is a parent ViewGroup&#8217;s onInterceptTouchEvent returning true partway through a gesture, most often something like a swipeable container deciding &#8220;this is actually a horizontal swipe, I&#8217;m taking over,&#8221; which sends the child an ACTION_CANCEL and steals every remaining event for that gesture. The child never sees the ACTION_UP, so any click listener attached to it silently never fires. Debugging usually means overriding dispatchTouchEvent temporarily to log every MotionEvent action and which View is being asked, since onInterceptTouchEvent never shows up in a stack trace and the actual interception can be happening several ancestors above the View you&#8217;re staring at.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What makes a Composable &#039;stable&#039;, and why can an unstable lambda parameter cause a whole list to recompose when only one item changed?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Compose Stability<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The Compose compiler decides whether it can skip recomposing something by checking if every parameter is stable, meaning its equals() reliably reflects whether it actually changed. Immutable types, String, the primitives, anything the compiler can prove is truly immutable, plus classes annotated @Immutable or @Stable, satisfy that. A plain data class with a var property, or one holding a MutableList, doesn&#8217;t, because the compiler can&#8217;t prove an existing instance won&#8217;t mutate silently without triggering an update.<\/p>\n<p>Lambdas passed into a Composable capture whatever&#8217;s in scope, and if that capture includes something unstable, an unstable class instance or a var referenced inline, the compiler can&#8217;t guarantee the lambda instance itself stays stable between recompositions even if functionally it&#8217;s identical, so it marks that parameter unstable and skips the optimization for anything depending on it. In a LazyColumn this shows up as every row recomposing when you tap one item, because the onClick lambda for each row captures the whole unstable list or an unstable ViewModel reference instead of just that row&#8217;s id. The fix is almost always narrowing what the lambda captures, pass a primitive id instead of a whole object, and you can confirm the real problem by turning on Compose compiler metrics, which report exactly which Composables and parameters got marked unstable and why.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Under a coroutine scope built on a regular Job versus a SupervisorJob, how does an unhandled exception in one child actually behave differently, and where does a CoroutineExceptionHandler need to sit to catch it?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Coroutine Exception Handling<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Under a plain Job, an unhandled exception in any child propagates to the parent, which cancels itself and, in turn, cancels every other child, because a regular Job treats one child&#8217;s failure as a failure of the whole unit of work, which is structured concurrency doing exactly what it&#8217;s designed to do. A SupervisorJob breaks that propagation at one level down, an exception in a direct child of the SupervisorJob doesn&#8217;t cancel the SupervisorJob or its siblings, which is exactly why viewModelScope and lifecycleScope are built on it internally, you don&#8217;t want one Fragment&#8217;s failed data load canceling every other coroutine that scope happens to be running.<\/p>\n<p>A CoroutineExceptionHandler only gets invoked for exceptions that would otherwise reach the top of the hierarchy uncaught, and it has to sit on the outermost scope&#8217;s context, not on an async builder, because a failure inside async is stored in the resulting Deferred and only rethrown when you call await(), so a handler on the scope never sees it, you have to wrap the await() call itself in try and catch. A frequent mistake is installing the handler on a child coroutine&#8217;s own context expecting it to catch that child&#8217;s failures, when it only takes effect for exceptions that propagate all the way to a coroutine with nowhere else to hand them off, so nesting it wrong means the exception gets silently absorbed by cancellation instead of ever reaching the handler.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">kotlin<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-kotlin\">\n\nval scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)\nscope.launch {\n\n    launch { throw RuntimeException(\u201cchild A failed\u201d) } \/\/ does not cancel scope or child B\n\n    launch { doWork() } \/\/ keeps running\n\n}\n<\/code><\/pre><\/div><\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">You&#039;ve got an intermittent crash in production, &#039;Only the original thread that created a view hierarchy can touch its views,&#039; but it never reproduces locally. How do you actually track it down?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Threading Crash Debugging<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The exception&#8217;s stack trace tells you which View call triggered the check, CalledFromWrongThreadException is thrown from ViewRootImpl.checkThread, called via requestLayout or invalidate, but it almost never tells you which line of your code originally launched the background work that ended up touching that view, so the crash trace by itself is usually a dead end. The real culprit is almost always a callback, a network response handler, a coroutine launched on Dispatchers.IO or Dispatchers.Default that updates UI directly instead of switching back with withContext(Dispatchers.Main), or a listener from a third-party SDK, analytics, Bluetooth, location, that Android doesn&#8217;t guarantee runs on the main thread even though it usually does.<\/p>\n<p>Because it&#8217;s intermittent, it&#8217;s almost always a race, the callback normally returns fast enough that some other main-thread event is already handling it by the time it fires, or the specific device or OS version where it actually crashes has different threading behavior for that one callback, which happens more than people expect with camera and Bluetooth APIs. Without a repro, the systematic path is enabling StrictMode&#8217;s detectAll() with penaltyLog() in a debug build and stress-testing the exact code paths on the crashing screen that touch views from callbacks, then grepping every listener registration on that screen and checking its documented threading contract instead of assuming, since one wrongly-assumed callback thread is the actual root cause in the large majority of these reports.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you structure Gradle modules in a large multi-module app to actually get faster builds, and why does the difference between api and implementation matter here?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Gradle Modularization<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The build-speed win from modularization comes from Gradle&#8217;s parallel and incremental build support, if module A changes and module B depends on it but module C doesn&#8217;t, Gradle only needs to rebuild A and B. That only works if the module graph is genuinely a graph and not secretly one blob where everything depends on everything, which is what happens when every module exposes dependencies with api instead of implementation. implementation means a dependency stays invisible outside that module and doesn&#8217;t leak onto the compile classpath of anything depending on it, so if module B uses implementation for a library, and module A depends on B, changing that library only forces a recompile of B, not A. api exposes the dependency transitively, so any change to it forces a recompile of B and everything downstream of B too.<\/p>\n<p>In practice this means feature modules like feature-checkout and feature-profile depending on a small set of core modules, core-network, core-ui, core-database, but never depending directly on each other, mediated instead through a thin navigation or contract module if one feature needs to trigger navigation into another, otherwise you get a circular dependency the build won&#8217;t even let you express, or a core module everything transitively depends on via api where changing one string resource forces a full rebuild of forty feature modules. Annotation processors like Hilt and Room run per module, so aggressive modularization without care can actually slow a clean build down even while speeding up incremental ones, which is why teams measure with the Gradle profiler before assuming more modules is automatically faster.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">kotlin<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-kotlin\">\n\n\/\/ feature module build.gradle.kts\n\ndependencies {\n\n    implementation(project(\u201c:core-network\u201d)) \/\/ not exposed downstream\n\n    api(project(\u201c:core-ui\u201d))                  \/\/ exposed to anything depending on this module\n\n}\n<\/code><\/pre><\/div><\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">When would you actually reach for a ContentProvider today, given FileProvider and WorkManager cover most of what people used to build them for?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">ContentProvider IPC<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A ContentProvider is still the right tool specifically when you need to expose structured, queryable data across a process boundary to apps you don&#8217;t control, the classic case being contacts, calendar, or media-store-style data where another app runs a query with a WHERE clause and gets a Cursor back, not just a file or a broadcast. That querying interface, backed by Binder IPC, is what a ContentProvider gives you that a shared file never does. FileProvider is itself a ContentProvider subclass, not really an alternative to one, it&#8217;s a narrower pre-built one Google ships specifically to hand out secure, temporary URIs to files without granting broad filesystem access, which covers &#8220;share this PDF to another app&#8221; but not &#8220;let another app search my locally cached data by keyword.&#8221;<\/p>\n<p>Where teams get it wrong today is reaching for a full custom ContentProvider for in-app-only data sharing between their own components, when a Room database plus a repository injected via Hilt does the same job with none of the Binder marshaling overhead and none of the manifest declaration and permission ceremony a ContentProvider requires, since IPC is unnecessary within the same process. The other legitimate remaining use is app startup: androidx.startup.Initializer is itself implemented as a ContentProvider under the hood, specifically because ContentProviders are guaranteed to be created before Application.onCreate() finishes, a guarantee that&#8217;s genuinely hard to get any other way.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">A screen showing a grid of thumbnails causes OutOfMemoryError crashes on lower-end devices even though each image file is only a few hundred KB. What&#039;s going on, and how do libraries like Glide or Coil avoid it?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Bitmap Memory OOM<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A JPEG&#8217;s file size on disk has almost nothing to do with its decoded size in memory. A decoded Bitmap stores one uncompressed value per pixel position, four bytes per pixel for ARGB_8888, so a 4000&#215;3000 photo from a modern phone camera decodes to roughly 48MB in memory regardless of a 300KB file size, and if your ImageView is only 200dp wide, you&#8217;re holding 48MB to display something that could have been decoded at a fraction of that size.<\/p>\n<p>Glide and Coil both solve this the same fundamental way. They use BitmapFactory.Options.inJustDecodeBounds to read the image&#8217;s dimensions without allocating the full bitmap, calculate an inSampleSize based on the actual target ImageView size so the image is decoded already downsampled to close to what&#8217;s needed, and reuse an existing Bitmap&#8217;s backing memory via inBitmap, bitmap pooling, instead of allocating a fresh one for every image, which matters a lot in a scrolling grid where thumbnails constantly get recycled.<\/p>\n<p>Diagnosing this means checking bitmap allocations in the Memory Profiler while scrolling the grid; bitmap sizes far larger than the ImageView&#8217;s actual pixel dimensions are the signature of loading a full-res source without ever specifying a target size or override(). The other thing worth checking is RecyclerView items that don&#8217;t cancel an in-flight image load when their ViewHolder gets recycled for a different item, which shows up as thumbnails briefly flashing the wrong image and memory climbing from multiple concurrent decodes racing for the same ImageView instead of the old load getting canceled when a new one starts on that holder.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-callout iq-callout--insight not-prose\"><div class=\"iq-callout__title\">What we see in Android mock interviews on LastRoundAI<\/div><div class=\"iq-callout__body\"><\/p>\n<p>Across the Android sessions candidates run through LastRoundAI&#8217;s mock interview product, the pattern that shows up over and over isn&#8217;t a knowledge gap, it&#8217;s a confidence gap on follow-ups. Someone explains remember vs. rememberSaveable correctly, then freezes the moment the interviewer asks what happens if the Activity gets killed by the system instead of just recreated. The first answer was memorized. The second question tests whether you actually understand it.<\/p>\n<p>The candidates who improve fastest aren&#8217;t the ones who learn more facts, they&#8217;re the ones who practice narrating their reasoning under a follow-up question, out loud, before the real interview. That&#8217;s a rehearsal problem, not a knowledge problem, and it&#8217;s the gap a live mock interview closes that a static question list can&#8217;t.<\/p>\n<p><\/div><\/div>\n<p><script type=\"application\/ld+json\">\n{\n  \"@context\": \"https:\/\/schema.org\",\n  \"@type\": \"FAQPage\",\n  \"mainEntity\": [\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Do Android interviews still ask about XML layouts and Views, or is it all Compose now?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Most 2026 loops lead with Compose since it's Google's recommended toolkit, but the View system isn't gone. Expect at least a light question about how the two interoperate if the company's app predates Compose.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Is Java still acceptable in Android coding rounds, or do I need Kotlin specifically?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Kotlin is the default expectation for almost every Android role advertised in 2026. A handful of legacy codebases still interview in Java, but prepare in Kotlin unless the job description says otherwise.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"How much system design should I expect for a mid-level Android role?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Less than a backend round, usually. Expect app-level design questions like offline-first sync strategy or feature module structure, not distributed systems questions about sharding a database.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Do companies ask LeetCode-style algorithm questions in Android interviews?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Yes, typically Easy to Medium difficulty covering arrays, hash maps, and basic graph or tree problems. It's usually one round out of several, not the entire loop.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"What's the single most common reason strong Android candidates fail the interview?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Being able to build features but not explain the reasoning behind lifecycle or memory-related decisions when interviewers push with a follow-up question.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Should I learn Kotlin Multiplatform (KMP) before an Android interview?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Not required for most roles in 2026, but worth mentioning if you've used it. Don't over-invest prep time unless the job description specifically mentions cross-platform work.\"\n      }\n    }\n  ]\n}\n<\/script><\/p>\n<div class=\"iq-related not-prose\"><div class=\"iq-related__title\">Related interview guides<\/div><div class=\"iq-related__grid\"><a class=\"iq-rel__card\" href=\"https:\/\/lastroundai.com\/interview-questions\/google-l4\"><span class=\"iq-rel__t\">Google L4 Interview Questions (2026): What They Actually Ask<\/span><span class=\"iq-rel__arrow\" aria-hidden=\"true\">&rarr;<\/span><\/a><a class=\"iq-rel__card\" href=\"https:\/\/lastroundai.com\/interview-questions\/meta-e5\"><span class=\"iq-rel__t\">Meta E5 Interview Questions (2026): What They Actually Ask<\/span><span class=\"iq-rel__arrow\" aria-hidden=\"true\">&rarr;<\/span><\/a><a class=\"iq-rel__card\" href=\"https:\/\/lastroundai.com\/interview-questions\/amazon-sde-2\"><span class=\"iq-rel__t\">Amazon SDE-2 Interview Questions (2026): What They Actually Ask<\/span><span class=\"iq-rel__arrow\" aria-hidden=\"true\">&rarr;<\/span><\/a><a class=\"iq-rel__card\" href=\"https:\/\/lastroundai.com\/interview-questions\/microsoft-sde\"><span class=\"iq-rel__t\">Microsoft SDE-2 Interview Questions (2026): What They Actually Ask<\/span><span class=\"iq-rel__arrow\" aria-hidden=\"true\">&rarr;<\/span><\/a><a class=\"iq-rel__card\" href=\"https:\/\/lastroundai.com\/interview-questions\/uber\"><span class=\"iq-rel__t\">Uber Interview Questions (2026): What They Actually Ask<\/span><span class=\"iq-rel__arrow\" aria-hidden=\"true\">&rarr;<\/span><\/a><a class=\"iq-rel__card\" href=\"https:\/\/lastroundai.com\/interview-questions\/tcs\"><span class=\"iq-rel__t\">TCS Interview Questions (2026): NQT, Technical, MR &#038; HR Rounds<\/span><span class=\"iq-rel__arrow\" aria-hidden=\"true\">&rarr;<\/span><\/a><\/div><\/div>\n<div class=\"iq-sources not-prose\"><div class=\"iq-sources__title\">Sources &amp; further reading<\/div><ul class=\"iq-sources__list\"><li><a href=\"https:\/\/developer.android.com\/jetpack\/compose\" target=\"_blank\" rel=\"nofollow noopener\">Android Developers: Jetpack Compose<\/a><\/li><li><a href=\"https:\/\/developer.android.com\/topic\/architecture\" target=\"_blank\" rel=\"nofollow noopener\">Android Developers: Guide to App Architecture<\/a><\/li><li><a href=\"https:\/\/kotlinlang.org\/docs\/coroutines-guide.html\" target=\"_blank\" rel=\"nofollow noopener\">Kotlin Docs: Coroutines Guide<\/a><\/li><li><a href=\"https:\/\/survey.stackoverflow.co\/2025\/technology\" target=\"_blank\" rel=\"nofollow noopener\">Stack Overflow Developer Survey 2025<\/a><\/li><li><a href=\"https:\/\/www.bls.gov\/ooh\/computer-and-information-technology\/software-developers.htm\" target=\"_blank\" rel=\"nofollow noopener\">BLS Occupational Outlook: Software Developers<\/a><\/li><\/ul><\/div>\n","protected":false},"excerpt":{"rendered":"<p>Google&#8217;s own developer documentation says it plainly: &#8220;Jetpack Compose is Android&#8217;s recommended modern toolkit for building native UI.&#8221; That&#8217;s not a slide from a keynote, it&#8217;s the current guidance every serious Android team reads, and it has quietly rewritten what a 2026 interview loop actually tests. I think most &#8220;Android interview questions&#8221; lists still circulating&#8230;<\/p>\n","protected":false},"author":3,"featured_media":1623,"comment_status":"open","ping_status":"closed","template":"","meta":{"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"tags":[],"class_list":["post-1146","iq","type-iq","status-publish","has-post-thumbnail","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.8 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Android Developer Interview Questions (2026) | LastRoundAI<\/title>\n<meta name=\"description\" content=\"Real Android interview questions for 2026: Kotlin, Compose, lifecycle, and architecture rounds, plus one full coding exercise and prep tips that work.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/lastroundai.com\/interview-questions\/android-developer\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Android Developer Interview Questions (2026) | LastRoundAI\" \/>\n<meta property=\"og:description\" content=\"Real Android interview questions for 2026: Kotlin, Compose, lifecycle, and architecture rounds, plus one full coding exercise and prep tips that work.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/lastroundai.com\/interview-questions\/android-developer\" \/>\n<meta property=\"og:site_name\" content=\"LastRound AI\" \/>\n<meta property=\"og:image\" content=\"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-android-developer-og.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"44 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/android-developer\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/android-developer\",\"name\":\"Android Developer Interview Questions (2026) | LastRoundAI\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/android-developer#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/android-developer#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-android-developer-og.png\",\"datePublished\":\"2026-07-22T03:54:00+00:00\",\"description\":\"Real Android interview questions for 2026: Kotlin, Compose, lifecycle, and architecture rounds, plus one full coding exercise and prep tips that work.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/android-developer#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/android-developer\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/android-developer#primaryimage\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-android-developer-og.png\",\"contentUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-android-developer-og.png\",\"width\":1200,\"height\":630,\"caption\":\"Android Developer interview questions \u2014 LastRoundAI\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/android-developer#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/lastroundai.com\\\/blog\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Interview Questions\",\"item\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Android Developer Interview Questions (2026): Most Asked Q&#038;A (Kotlin)\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/\",\"name\":\"LastRound AI\",\"description\":\"Interview Assistant prep, tech careers and AI tools\",\"publisher\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#organization\",\"name\":\"LastRound AI\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png\",\"contentUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png\",\"width\":400,\"height\":400,\"caption\":\"LastRound AI\"},\"image\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Android Developer Interview Questions (2026) | LastRoundAI","description":"Real Android interview questions for 2026: Kotlin, Compose, lifecycle, and architecture rounds, plus one full coding exercise and prep tips that work.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/lastroundai.com\/interview-questions\/android-developer","og_locale":"en_US","og_type":"article","og_title":"Android Developer Interview Questions (2026) | LastRoundAI","og_description":"Real Android interview questions for 2026: Kotlin, Compose, lifecycle, and architecture rounds, plus one full coding exercise and prep tips that work.","og_url":"https:\/\/lastroundai.com\/interview-questions\/android-developer","og_site_name":"LastRound AI","og_image":[{"width":1200,"height":630,"url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-android-developer-og.png","type":"image\/png"}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"44 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/lastroundai.com\/interview-questions\/android-developer","url":"https:\/\/lastroundai.com\/interview-questions\/android-developer","name":"Android Developer Interview Questions (2026) | LastRoundAI","isPartOf":{"@id":"https:\/\/lastroundai.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/lastroundai.com\/interview-questions\/android-developer#primaryimage"},"image":{"@id":"https:\/\/lastroundai.com\/interview-questions\/android-developer#primaryimage"},"thumbnailUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-android-developer-og.png","datePublished":"2026-07-22T03:54:00+00:00","description":"Real Android interview questions for 2026: Kotlin, Compose, lifecycle, and architecture rounds, plus one full coding exercise and prep tips that work.","breadcrumb":{"@id":"https:\/\/lastroundai.com\/interview-questions\/android-developer#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/lastroundai.com\/interview-questions\/android-developer"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/lastroundai.com\/interview-questions\/android-developer#primaryimage","url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-android-developer-og.png","contentUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-android-developer-og.png","width":1200,"height":630,"caption":"Android Developer interview questions \u2014 LastRoundAI"},{"@type":"BreadcrumbList","@id":"https:\/\/lastroundai.com\/interview-questions\/android-developer#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/lastroundai.com\/blog"},{"@type":"ListItem","position":2,"name":"Interview Questions","item":"https:\/\/lastroundai.com\/interview-questions"},{"@type":"ListItem","position":3,"name":"Android Developer Interview Questions (2026): Most Asked Q&#038;A (Kotlin)"}]},{"@type":"WebSite","@id":"https:\/\/lastroundai.com\/blog\/#website","url":"https:\/\/lastroundai.com\/blog\/","name":"LastRound AI","description":"Interview Assistant prep, tech careers and AI tools","publisher":{"@id":"https:\/\/lastroundai.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/lastroundai.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/lastroundai.com\/blog\/#organization","name":"LastRound AI","url":"https:\/\/lastroundai.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/lastroundai.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/06\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png","contentUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/06\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png","width":400,"height":400,"caption":"LastRound AI"},"image":{"@id":"https:\/\/lastroundai.com\/blog\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/1146","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq"}],"about":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/types\/iq"}],"author":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/comments?post=1146"}],"version-history":[{"count":3,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/1146\/revisions"}],"predecessor-version":[{"id":1741,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/1146\/revisions\/1741"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/media\/1623"}],"wp:attachment":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/media?parent=1146"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/tags?post=1146"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}