Interview track

Android Developer interview prep

A spaced-repetition deck of 161+ Android Developer interview questions — organised by topic and difficulty, and scheduled for review based on your ratings. Preview a few cards below, then choose access to study the whole track on an Anki-style SM-2 schedule.

161 cards10 topics
See access options

Eligible new monthly or yearly subscribers get 7 days free. Checkout confirms eligibility and charges.

Before you start

Start with basic Kotlin and experience building a small Android app. Shared algorithms include Python examples; practise translating those ideas into Kotlin.

First pass: Kotlin & the Type System → Coroutines & Flow. Begin with the foundational questions in these modules, then follow the outline. Revisit advanced and senior questions as the job requires; all cards remain available from the start.

Shared topics can appear in several tracks and keep one review history. The outline labels shared foundations and role-specific modules; choose your depth for the job you are preparing for.

What's covered

Every topic in this track, grouped the way you'd study it.

Kotlin & the Type System

11 cards

Role-specific module

Kotlin & Types

Coroutines & Flow

12 cards

Role-specific module

Coroutines & Flow

Components & Lifecycle

10 cards

Role-specific module

Components & Lifecycle

UI: Views & Jetpack Compose

11 cards

Role-specific module

Views & Compose

Android App Architecture

10 cards

Role-specific module

Architecture

Networking & Persistence

10 cards

Role-specific module

Networking & Persistence

Performance & Internals

10 cards

Role-specific module

Performance & Internals

Testing & Tooling

9 cards

Role-specific module

Testing & Tooling

CS Fundamentals

43 cards

Shared foundations

Data Structures & Algorithms

Behavioral

35 cards

Shared foundations

Behavioral

Sample questions

A few cards from the deck — reveal each answer, then choose access to study the full set on a schedule.

How does val differ from var, and does val make an object immutable?

Short answer: var allows reassignment; val does not. Neither makes the referenced object immutable. List<T> is a read-only interface, so another alias can still mutate its backing collection.

val backing = mutableListOf(1, 2)
val view: List<Int> = backing
backing.add(3)
println(view) // [1, 2, 3]: read-only is not immutable
val snapshot = backing.toList()
backing.add(4)
println(snapshot) // [1, 2, 3]

In depth:

  1. A stored val keeps the same reference. A property with a custom getter can return a different value on each access: val now: Long get() = System.currentTimeMillis().
  2. toList() creates a separate shallow snapshot: structural changes to the source do not affect it, but mutable element objects remain shared. Deep immutability also requires immutable elements and controlled ownership.
  3. const val is a compile-time constant declared at top level or inside an object/companion object, with an eligible primitive or String value.

Watch out: val plus List prevents reassignment and writes through that interface; it is not a guarantee that the object graph cannot change.

Sources: Kotlin collections, copying collections.

What is a coroutine, and how does suspend differ from blocking a thread?

Short answer: A coroutine is a lightweight unit of concurrency on top of threads. Suspension parks the coroutine and frees the thread for other work; blocking keeps the thread busy. That is why thousands of coroutines can live on a single thread.

In depth:

// Blocking: the thread is busy and useless
fun blocking() {
    Thread.sleep(1000)   // the thread sleeps — unavailable to anyone
}

// Suspension: the coroutine parks, the thread stays free
suspend fun suspending() {
    delay(1000)          // meanwhile the thread runs other coroutines
}

fun main() = runBlocking {
    repeat(100_000) { launch { delay(1000) } }  // fine even on one thread
    // 100,000 threads with Thread.sleep(1000) — OutOfMemoryError
}
  1. Suspension ≠ blocking — delay hands control back to the dispatcher; Thread.sleep holds the thread hostage.
  2. Lightweight — a coroutine is a heap object (continuation + state), not a ~1 MB stack like a thread.
  3. Cooperative — a coroutine yields its thread only at suspension points.

⚠️ Common mistake: saying a suspend function runs on a background thread. It does not: suspend by itself never switches threads — the dispatcher decides where the code runs.

Walk through the full Activity lifecycle — which callbacks fire and what each is for?

Short answer: Three "up" callbacks — onCreate → onStart → onResume — bring the screen to the user; three "down" ones — onPause → onStop → onDestroy — take it away. onRestart fires when a stopped Activity comes back to the screen.

In depth:

     onCreate()    ← created: setContentView, init, read savedInstanceState
        │
     onStart()     ← became visible
        │
     onResume()    ← in the foreground, receiving input
        │
   [ RESUMED — running ]
        │
     onPause()     ← lost focus (partially covered)
        │
     onStop()      ← fully hidden  ──onRestart()→onStart() when it returns
        │
     onDestroy()   ← destroyed (finish or low memory)
  1. onCreate — the only mandatory one; layout inflation, initialization, restoring from savedInstanceState.
  2. onStart / onStop — the visibility boundary: register and unregister whatever only a visible screen needs.
  3. onResume / onPause — the focus and input boundary; onPause must be short — the next screen won't appear until it returns.
  4. onDestroy — the finale; may never arrive if the process is killed, so save critical things earlier.

⚠️ Common mistake: treating onDestroy as a guaranteed place to save. On process death it never runs — save in onSaveInstanceState / onStop.

What is recomposition and what triggers it?

Short answer: Recomposition is re-invoking @Composable functions when the state they read changes. It is triggered by a write to an observable State (mutableStateOf, a StateFlow via collectAsState, etc.) that was read inside the composable. Only the functions that read that particular value re-run.

In depth:

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    Text("Clicks: $count")           // reads count → re-invoked
    Button(onClick = { count++ }) {  // writes count → recomposition
        Text("Tap")                  // doesn't read count → skipped
    }
}
  1. The trigger is not "any change" — it is a write to snapshot state that was read in a specific composable. Compose tracks reads and invalidates exactly the affected scopes.
  2. Optimistic — if state changes again mid-recomposition, the current pass is discarded and restarted.
  3. Unordered and possibly parallel — the execution order of sibling composables is not guaranteed; don't rely on side effects in the body.

⚠️ Common mistake: a plain var without mutableStateOf is not state; Compose can't see its change and won't recompose — the UI freezes.

What is MVVM, and why must a ViewModel never hold a reference to a View or Context?

Short answer: MVVM splits a screen into three roles: Model (data and logic), View (Activity/Fragment/Composable — it only renders and forwards actions), and ViewModel (holds state and survives View recreation). The ViewModel knows nothing about the concrete View — it just publishes state the View subscribes to.

In depth:

┌────────┐   subscribe to state  ┌───────────┐   request data  ┌───────┐
│  View  │ ◄─────────────────── │ ViewModel │ ──────────────► │ Model │
│ (dumb) │ ── actions (events) ─►│  (state)  │ ◄─── data ────── │(repo) │
└────────┘                       └───────────┘                 └───────┘
  1. View is a dumb renderer — it takes ready-made state and draws it; no business logic, just "show what I was given".
  2. ViewModel holds state — it survives rotation (the ViewModelStoreOwner keeps it, rather than recreating it with the Activity).
  3. Model — repositories, DB, network; the ViewModel talks to these, not the View directly.

⚠️ Common mistake: storing a View, Activity, or Context in a ViewModel field. The ViewModel outlives the View — after a rotation the reference points at a destroyed Activity, which is a memory leak. Need a Context? Use AndroidViewModel.getApplication() or inject @ApplicationContext.

What is the Retrofit + OkHttp stack made of, and what does each layer do?

Short answer: Retrofit is the top, declarative layer: it turns an annotated Kotlin interface into HTTP requests and parses the response via a Converter. OkHttp beneath it is the actual HTTP client: sockets, connection pool, cache, interceptors, timeouts, retries. Retrofit does not work without OkHttp — it is its transport.

In depth:

┌─────────────────────────────────────────┐
│ ApiService (interface + @GET/@POST)      │  ← your code
├─────────────────────────────────────────┤
│ Retrofit: annotations → Request,         │
│           Converter (JSON ↔ data class)  │
├─────────────────────────────────────────┤
│ OkHttp: interceptors, connection pool,   │
│         cache, timeouts, TLS, retry      │
├─────────────────────────────────────────┤
│ Socket / network                         │
└─────────────────────────────────────────┘
  1. Retrofit — understands suspend and returns a ready data class; it does not parse JSON itself.
  2. Converter — kotlinx.serialization / Moshi / Gson; this is what maps JSON ↔ object.
  3. OkHttp — all the networking machinery; one OkHttpClient per app so the pool and cache are reused.
  4. CallAdapter — suspend-function support out of the box.

⚠️ Common mistake: creating a new OkHttpClient/Retrofit per request. That throws away the connection pool and cache; the client should be a single instance for the whole app.

Ready to make it stick?

Explore a sample, choose a track, and build a review routine that fits your preparation.

Questions about this track

How should I prepare for an Android Developer interview?

Study the concepts you'll be asked to explain, not just the ones you can code. RecallDeck's Android Developer track gives you 161+ curated interview questions and resurfaces each one with an Anki-style SM-2 schedule based on your ratings — to practise recalling and explaining them. Combine reviews with coding and mock interviews.

What topics does the Android Developer track cover?

The Android Developer track is organised into the core areas Android Developer interviews actually test, grouped by topic and by difficulty (Concept, Junior, Middle, Senior). You can preview the full outline and sample questions above before signing in.

Is spaced repetition effective for Android Developer interview prep?

Yes. Active recall and spaced practice can help retain what you study. Grade yourself honestly and check your understanding with practical work. RecallDeck schedules each Android Developer card using your ratings, so familiar cards return less often and difficult ones get more practice.

Can I try the Android Developer track before paying?

Eligible new monthly or yearly subscribers can try the complete Android Developer track and every feature for seven days. Checkout confirms trial eligibility and the amount due; returning subscribers may be charged immediately. Cancel online before the trial ends to avoid its first charge. Lifetime access has no trial.

Other interview tracks