๐ŸŽฏ Android Interview Preparation and Career

Real-World Android Coding Interview Problems

Many Android coding rounds aren't abstract puzzles. They ask you to build a small, realistic feature well, under time pressure. Here are common problem types, with Kotlin solutions and what interviewers evaluate.

โœ๏ธ Yunok Tech ๐Ÿ“… Updated โฑ 3 min read ๐ŸŽฏ Intermediate
On this page
  1. What interviewers evaluate
  2. Problem 1: Search with debounce and cancellation
  3. Problem 2: LRU cache
  4. Problem 3: Rate limiter (token bucket)
  5. Problem 4: Paginated list with error and retry (feature task)
  6. Problem 5: Group and summarise transactions
  7. Tips for the round
  8. Frequently asked questions
  9. Conclusion

In a machine-coding or take-home round, a working feature is the minimum. What earns a strong signal is clean structure, correct state handling, error and edge cases, and a few meaningful tests, all explained clearly.

What interviewers evaluate

Coding round evaluation criteria Correctness, structure and readability, state and lifecycle handling, edge cases and errors, testing, and communication about trade-offs. Correctness Structure State/lifecycle Edge cases Tests Communication
Talk while you code: state assumptions, mention what you'd do with more time.

Problem 1: Search with debounce and cancellation

Build a search ViewModel that waits 300 ms after typing stops, ignores blank queries, cancels outdated searches, and exposes loading, results and error states.

SearchViewModel.ktKotlin
sealed interface SearchState {
    data object Idle : SearchState
    data object Loading : SearchState
    data class Results(val items: List<String>) : SearchState
    data class Error(val message: String) : SearchState
}

@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
class SearchViewModel(private val api: suspend (String) -> List<String>) : ViewModel() {
    private val query = MutableStateFlow("")

    val state: StateFlow<SearchState> = query
        .map(String::trim)
        .debounce(300)
        .distinctUntilChanged()
        .flatMapLatest { q ->
            if (q.isEmpty()) flowOf(SearchState.Idle)
            else flow<SearchState> {
                emit(SearchState.Loading)
                emit(SearchState.Results(api(q)))
            }.catch { emit(SearchState.Error("Search failed")) }
        }
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), SearchState.Idle)

    fun onQueryChange(q: String) { query.value = q }
}

Discuss: why flatMapLatest (cancels stale requests), why distinctUntilChanged, and how you'd test it with virtual time.

Problem 2: LRU cache

Implement a fixed-capacity least-recently-used cache with O(1) get and put.

LruCache.ktKotlin
class LruCache<K, V>(private val capacity: Int) {
    init { require(capacity > 0) }
    // accessOrder = true: iteration order is least โ†’ most recently accessed
    private val map = object : LinkedHashMap<K, V>(capacity, 0.75f, true) {
        override fun removeEldestEntry(eldest: MutableMap.MutableEntry<K, V>?) = size > capacity
    }

    @Synchronized fun get(key: K): V? = map[key]
    @Synchronized fun put(key: K, value: V) { map[key] = value }
    @Synchronized fun size(): Int = map.size
}

Discuss: thread safety, why LinkedHashMap with access order works, and Android's built-in LruCache with size-of for bitmaps.

Problem 3: Rate limiter (token bucket)

TokenBucket.ktKotlin
class TokenBucket(
    private val capacity: Int,
    private val refillPerSecond: Double,
    private val now: () -> Long = System::nanoTime,
) {
    private var tokens = capacity.toDouble()
    private var last = now()

    @Synchronized
    fun tryAcquire(): Boolean {
        val t = now()
        tokens = minOf(capacity.toDouble(), tokens + (t - last) / 1e9 * refillPerSecond)
        last = t
        return if (tokens >= 1) { tokens -= 1; true } else false
    }
}
// Use: limit AI or API calls per user action burst; inject `now` for deterministic tests.

Problem 4: Paginated list with error and retry (feature task)

Show a list from a paged API, load more near the end, show a footer spinner or retry, and keep state across rotation. Approach: Paging 3 if allowed (explain the choice), otherwise a manual pager:

Manual pager stateKotlin
data class ListState(
    val items: List<Item> = emptyList(),
    val nextPage: Int? = 1,
    val loadingMore: Boolean = false,
    val error: String? = null,
)

fun loadMore() {
    val s = _state.value
    if (s.loadingMore || s.nextPage == null) return            // guard duplicate and end-of-list loads
    _state.update { it.copy(loadingMore = true, error = null) }
    viewModelScope.launch {
        _state.update { cur ->
            try {
                val page = api.page(s.nextPage)
                cur.copy(items = cur.items + page.items, nextPage = page.next, loadingMore = false)
            } catch (e: IOException) {
                cur.copy(loadingMore = false, error = "Couldn't load more")
            }
        }
    }
}

Note: calling a suspend function inside update { } isn't allowed, because the lambda isn't suspending. In the interview, fetch first, then update. Spotting this kind of detail is exactly what interviewers watch for.

CorrectedKotlin
viewModelScope.launch {
    val result = runCatching { api.page(s.nextPage) }
    _state.update { cur ->
        result.fold(
            onSuccess = { page -> cur.copy(items = cur.items + page.items, nextPage = page.next, loadingMore = false) },
            onFailure = { cur.copy(loadingMore = false, error = "Couldn't load more") },
        )
    }
}

Problem 5: Group and summarise transactions

Collections problemKotlin
data class Txn(val category: String, val amountPaise: Long, val date: LocalDate)

/** Top N categories by spend in a month, with totals. */
fun topCategories(txns: List<Txn>, month: YearMonth, n: Int): List<Pair<String, Long>> =
    txns.asSequence()
        .filter { YearMonth.from(it.date) == month }
        .groupingBy { it.category }
        .fold(0L) { acc, t -> acc + t.amountPaise }
        .entries
        .sortedByDescending { it.value }
        .take(n)
        .map { it.key to it.value }

Tips for the round

  1. Clarify requirements and constraints before coding.
  2. Start with the data model and state, then logic, then UI.
  3. Handle loading, empty and error states, and rotation.
  4. Add one or two focused tests if time allows.
  5. Summarise trade-offs and next steps at the end.

Frequently asked questions

Should I use libraries in a take-home?

Use standard ones (Retrofit, Room, Compose), unless told otherwise, and explain why. Avoid exotic dependencies.

DS&A or Android tasks?

Companies differ. Product companies often include both. Practise common algorithm patterns in Kotlin as well as feature tasks.

Conclusion

Android coding rounds reward correct, well-structured, state-aware code with edge cases, a few tests and clear communication. Practise realistic features (debounced search, pagination, caching) and classic Kotlin collection problems.

Next step: prepare for legacy-code questions with Java to Kotlin Migration Interview Questions.

Official references

๐Ÿ“ฑ Apps built by Yunok Tech

Real Android apps on Google Play, free to download.

See all our apps โ†’

Ready to build real Android applications?

Interviews reward developers who can explain why, not just how. Build the Kotlin, Compose and architecture depth that lets you discuss real trade-offs with confidence.

Move beyond theory and strengthen your Android development skills with Yunoktech's Android course. Learn core concepts, practical implementation, modern Android development techniques, and the reasoning behind building maintainable applications.

Explore the Android course and take your next step as an Android developer.

Share: LinkedIn X / Twitter WhatsApp