Sitemap
Sankamitasaha

Sankamitasaha

Android developer with 11+ years of experience, exploring deeper Android engineering, AI, and modern software development.

5 Android Interview Questions That Made Me Think Beyond the Basics

6 min readOct 5, 2026

--

After several years of working with Android, I realized something recently: knowing how to use a technology is very different from understanding what is happening underneath it.

I had been preparing for Android interviews by revising familiar topics — Kotlin, Coroutines, Room, Retrofit, Jetpack Compose, MVVM and Clean Architecture.

Then I faced questions that were less about definitions and more about real situations.

Questions like:

  • What happens when a production database needs a schema change?
  • When should you use an OkHttp Interceptor versus an Authenticator?
  • What happens when a coroutine fails?
  • Why is a Compose UI recomposing unnecessarily?
  • What happens to WorkManager work when the app is killed?

These questions made me realize that senior-level Android interviews are not only testing whether we know an API. They are testing whether we understand why something happens and how we would handle it in a real application.

So I decided to document what I am learning, one topic at a time.

Here are five questions that made me look beyond the basics.

1. Room Migration — What happens when your production database changes?

Imagine an application is already installed on thousands of devices.

The current database is version 1:

User
-----
id
name

Now, in a new version of the app, we want to add:

email

We update the Entity and increase the Room database version.

But what happens to users who already have version 1 of the database on their phones?

We cannot simply assume that the new schema will replace the old one.

We need a migration path.

Room supports both automatic and manual migrations. Automatic migrations can handle many basic schema changes, while more complex changes may require a manually defined Migration.

For example, a simple migration might look conceptually like:

val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(connection: SQLiteConnection) {
connection.execSQL(
"ALTER TABLE User ADD COLUMN email TEXT"
)
}
}

And then the migration needs to be registered with the database builder.

The important interview question is not just:

“What is Room migration?”

The more important question is:

“What happens if a user is on an older database version and there is no migration path?”

If Room cannot find a migration path, opening the database can fail unless an appropriate fallback strategy has been configured. Destructive migration can recreate the database, but it can also permanently delete the existing data.

This is why migration testing is important, especially for production applications where existing user data matters.

My takeaway:
Room migration is not simply about increasing the database version. It is about safely moving existing user data from one schema to another.

2. OkHttp Interceptor vs Authenticator — When should you use each?

This is another question that initially looks simple.

An Interceptor can be used to inspect or modify requests and responses. For example, we can add an authentication header to outgoing requests.

A simplified example:

class AuthInterceptor : Interceptor {    
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
.newBuilder()
.addHeader("Authorization", "Bearer $token")
.build()
return chain.proceed(request)
}
}

But then consider a real production scenario.

The access token has expired.

The application sends a request.

The server responds with 401 Unauthorized.

Now what?

This is where an Authenticator becomes important.

Instead of simply adding a token to every request, the authentication flow can react to an authentication failure and attempt to obtain a new credential before retrying the request.

This leads to a much better interview discussion:

Interceptor: “I want to modify or observe the request/response.”

Authenticator: “Authentication failed. Can I obtain valid credentials and retry?”

And then the deeper questions begin:

  • What happens if multiple requests receive 401 at the same time?
  • How do we prevent multiple token-refresh requests?
  • What happens if the refresh token is also expired?
  • How do we avoid an infinite retry loop?
  • What should happen to requests waiting for the new token?

This is where knowing the definition is no longer enough.

My takeaway:
Understanding the responsibility of each component is more important than memorizing their definitions.

3. Coroutine Exception Handling — What happens when a child coroutine fails?

We often write something like:

viewModelScope.launch {
repository.loadData()
}

But what happens when loadData() throws an exception?

The answer depends on the coroutine hierarchy and how the coroutine was launched.

Kotlin coroutines use structured concurrency. In a regular coroutine hierarchy, an exception from a child can propagate to its parent and cause cancellation of related work. SupervisorJob and supervisorScope provide different behavior where one child's failure does not automatically cancel its siblings.

Get Sankamitasaha’s stories in your inbox

Join Medium for free to get updates from this writer.

For example, imagine a screen loading three independent pieces of information:

User details
Transactions
Offers

If the Offers API fails, should the entire screen fail?

Maybe not.

We may want:

User details     → Success
Transactions → Success
Offers → Failed

This is a situation where supervision can be useful.

Another important point is CoroutineExceptionHandler.

A common misconception is:

“I can add CoroutineExceptionHandler everywhere and it will catch all coroutine exceptions.”

That’s not how it works.

CoroutineExceptionHandler is intended for uncaught exceptions, particularly at the root of a coroutine hierarchy. Child coroutine exceptions normally propagate to their parent instead. Also, async exposes failures through the resulting Deferred, so the exception is normally handled when await() is called.

My takeaway:
For coroutines, knowing launch, async, try-catch, and CoroutineExceptionHandler individually isn't enough. We need to understand the coroutine hierarchy and exception propagation.

4. Jetpack Compose — Why is my UI recomposing unnecessarily?

One of the biggest differences between basic Compose knowledge and deeper Compose knowledge is understanding recomposition.

When state read by a composable changes, Compose can schedule recomposition for the relevant parts of the UI. Recomposition means Compose re-executes composables that may have changed and updates the Composition.

But here’s the interesting question:

“My screen works correctly, so why is it recomposing so many times?”

This becomes a performance question.

Compose has three major phases:

Composition
↓
Layout
↓
Drawing

A state read during composition can cause recomposition when that state changes. In some situations, we can defer state reads to later phases so that Compose does less work.

For example, rapidly changing values such as scroll position can potentially cause unnecessary work if they are read too high in the composition.

This is why concepts such as:

  • Stable types
  • State hoisting
  • remember
  • derivedStateOf
  • Lazy layouts
  • Deferring state reads
  • Understanding composition, layout and drawing phases

become important when discussing Compose performance.

The key point is:

Recomposition itself is not a problem. Unnecessary work during recomposition is what we need to investigate.

My takeaway:
Don’t try to avoid every recomposition. Understand why it is happening and whether the amount of work being performed is actually a problem.

5. WorkManager — What happens if the app is killed?

This is another question where the definition is easy:

“WorkManager is used for deferrable, persistent background work.”

But a senior-level discussion goes further.

Suppose an application needs to upload some data.

The user starts the operation and then:

  • closes the app,
  • removes it from the recent apps screen,
  • loses network connectivity,
  • restarts the phone.

What should happen?

For work that needs to be persistent and can be executed later, WorkManager is designed to provide a way to schedule that work with constraints such as network availability.

This leads to practical questions:

  • Is the work one-time or periodic?
  • What happens if the work fails?
  • Should it retry?
  • What constraints are required?
  • How do we make the work idempotent?
  • What happens if the same work is scheduled multiple times?
  • How do we observe the work status?

The important distinction is that WorkManager is not simply another way to start a background thread.

It is designed around persistent work that can be scheduled and retried according to constraints and application requirements.

My takeaway:
When choosing a background-work solution, the first question should not be “Which API do I remember?” It should be “What guarantees does this work actually require?”

Final thought

If you are an Android developer preparing for senior-level interviews, don’t only revise definitions.

Take one familiar topic and keep asking:

What happens if something goes wrong?

That question can take you much deeper than a list of interview definitions ever will.

--

--

Sankamitasaha
Sankamitasaha

Written by Sankamitasaha

Android developer with 11+ years of experience, exploring deeper Android engineering, AI, and modern software development.