Skip to content
tezvyn:

Top 30 Intermediate Android & Kotlin Interview Questions and Answers

30 intermediate multiple-choice Android & Kotlin interview questions, past the definitions: how the pieces fit together, what breaks in practice, and the trade-off behind a choice. They come from 30 bites in the Android & Kotlin library, the middle slice of the 245 Android & Kotlin interview questions in the library. Answer them here or read straight down. Every question carries the correct option, why it is correct, and a link to the bite it came from.

Jetpack Compose, Android Studio, Kotlin, Material You

30 questions. Pick an answer, or open “Show the answer” to read it.

Answers are graded in your browser. Nothing is saved, and no XP or streak is earned here. The app keeps score.

  1. Question 1 of 30

    If a class has a member function and an extension function is defined with the same signature, which one will be invoked when called on an instance of that class?

    Show the answer

    Answer: b · The member function, because member functions always take precedence.

    When a member function and an extension function have the same signature, the member function will always be chosen. This is because extension functions are resolved statically and do not override class members, making the member function the more specific choice.

    Read the full bite: What is a Kotlin extension function? Write one for String.

  2. Question 2 of 30

    Which statement accurately describes the underlying mechanism of Kotlin extension functions?

    Show the answer

    Answer: c · They are compiled into static utility methods where the receiver object is passed as the first parameter.

    The card explicitly states that extension functions are "syntactic sugar for static utility methods" and are "compiled down to static methods that take the receiver object as the first argument." They do not modify the original class or its bytecode, which is a common misconception.

    Read the full bite: What is a Kotlin extension function? Write one for String.

  3. Question 3 of 30

    Which statement accurately describes how Kotlin resolves an extension function call on a receiver?

    Show the answer

    Answer: c · It uses the compile-time declared type of the receiver, compiling down to a static method call

    Kotlin extension functions are resolved at compile time based on the declared receiver type and compile to static methods, not real class members. Option D is tempting because it mirrors true polymorphic dispatch, but extensions are statically dispatched and cannot be overridden.

    Read the full bite: What is an extension function? Write a hasWhitespace extension for String.

  4. Question 4 of 30

    When working with immutable state in Kotlin, which generated function of a data class is specifically designed to create a new object instance with modified properties without altering the original?

    Show the answer

    Answer: b · copy()

    The copy() function is specifically designed to create a new instance of the data class, allowing certain properties to be modified while leaving the original object unchanged, which is crucial for immutable state management. The other generated functions serve different purposes like comparison, string representation, or hash generation, not for creating modified copies.

    Read the full bite: What are the advantages of a Kotlin data class and its functions?

  5. Question 5 of 30

    When managing immutable state with a data class, what is the primary role of the generated `copy()` function?

    Show the answer

    Answer: b · To create a new, modified instance of the object, leaving the original unchanged.

    The `copy()` function is central to working with immutable objects; it creates a new instance with specified changes, leaving the original untouched. Option A is a critical misunderstanding, as `copy()` never mutates the original object, which is key to predictable state management.

    Read the full bite: What are the advantages of a Kotlin data class?

  6. Question 6 of 30

    When using a Kotlin data class as a key in a HashMap, which statement about its compiler-generated behavior is true?

    Show the answer

    Answer: b · Only properties defined in the primary constructor marked as val or var are used in generated equals and hashCode, so the key remains consistent in the map.

    The compiler only incorporates primary constructor properties marked val or var into generated equals and hashCode, guaranteeing the contractual consistency required for safe use as a HashMap key. Option C is a tempting distractor because developers often assume body properties participate in structural equality, but the compiler intentionally excludes them.

    Read the full bite: What are the primary advantages of using a data class?

  7. Question 7 of 30

    An Android Fragment needs a property for a complex, computationally expensive object that is only used in certain user flows. The object should be created only once. Which declaration is most appropriate?

    Show the answer

    Answer: b · private val myObject: MyObject by lazy { createExpensiveObject() }

    `val by lazy` is ideal for expensive, immutable properties because it defers creation until first access and caches the result. `lateinit var` is incorrect because the property is immutable and is meant for when an external framework provides the value, not for deferred computation.

    Read the full bite: lateinit var vs. val by lazy in Android

  8. Question 8 of 30

    Which statement accurately describes a fundamental difference between lateinit var and val by lazy in Kotlin?

    Show the answer

    Answer: b · val by lazy can initialize properties of any type, including primitives, unlike lateinit var.

    The card states that lateinit var cannot be used for primitive types like Int or Boolean, while val by lazy has no such restriction. Option A is incorrect because it describes the behavior of val by lazy, not lateinit var, which is initialized manually.

    Read the full bite: lateinit var vs. val by lazy in Android

  9. Question 9 of 30

    In an Activity, a property holds a ViewModel obtained via ViewModelProvider in onCreate. Why is lateinit var more suitable than val by lazy?

    Show the answer

    Answer: c · lateinit var allows imperative assignment and can be reassigned if the Activity is recreated

    lateinit var is designed for mutable properties assigned imperatively in lifecycle callbacks like onCreate and can be reassigned when the Activity is recreated, whereas val by lazy is immutable and self-initializing. Option D is tempting but wrong because it describes lazy's first-access caching behavior, not lateinit's imperative assignment model.

    Read the full bite: Explain lateinit var versus val by lazy in Android

  10. Question 10 of 30

    What core characteristic of Kotlin's function handling makes higher-order functions possible?

    Show the answer

    Answer: b · The treatment of functions as first-class citizens, allowing them to be passed as arguments or returned.

    The card explicitly states that higher-order functions are possible "because Kotlin treats functions as first-class citizens, meaning they can be stored in variables or passed as arguments." This fundamental concept allows functions to be used as parameters or return values. While type inference is a useful Kotlin feature, it does not enable the core mechanism of functions being treated as first-class entities.

    Read the full bite: Explain and implement a Kotlin higher-order function

  11. Question 11 of 30

    In Kotlin, what is the defining characteristic that makes a function a 'higher-order function'?

    Show the answer

    Answer: b · It accepts a function as an argument or returns a function as its result.

    The correct answer is right because the definition of a higher-order function is one that treats functions as first-class citizens by taking them as parameters or returning them. While many higher-order functions operate on collections (a tempting distractor), this is a common use case, not the defining property.

    Read the full bite: Explain and implement a higher-order function in Kotlin

  12. Question 12 of 30

    What distinguishes filterAndTransform as a higher-order function rather than merely a generic function in Kotlin?

    Show the answer

    Answer: d · It takes other functions as arguments, such as predicate and transform

    A higher-order function must accept or return a function, so taking predicate and transform as arguments makes it one. Option C describes generics, which alone do not make a function higher-order.

    Read the full bite: Explain higher-order functions and implement filterAndTransform

  13. Question 13 of 30

    Which behavior correctly describes exception propagation for a coroutine launched with launch inside a regular Job scope?

    Show the answer

    Answer: c · The exception propagates to the parent, which cancels siblings and fails the scope.

    Under a regular Job, a failing child propagates its exception upward, causing the parent to cancel all siblings and fail the scope. Distractor D describes SupervisorJob behavior, which must be explicitly used to prevent sibling cancellation.

    Read the full bite: How does Structured Concurrency handle cancellations and exceptions?

  14. Question 14 of 30

    In a standard CoroutineScope, if one child coroutine fails with an exception, what is the immediate effect on its siblings and the parent scope?

    Show the answer

    Answer: b · The failing coroutine cancels its siblings, and the exception then propagates up to cancel the parent scope.

    Structured concurrency with a standard Job follows an 'all-for-one' policy. An uncaught exception in one child cancels its siblings and then propagates to the parent, cancelling the entire scope. The behavior where siblings continue running is characteristic of a SupervisorJob.

    Read the full bite: Structured Concurrency in Kotlin Coroutines

  15. Question 15 of 30

    In Kotlin's Structured Concurrency, what is the default outcome if one child coroutine launched with launch throws an uncaught exception?

    Show the answer

    Answer: b · The parent scope and all its sibling coroutines are immediately cancelled.

    With a default Job, an uncaught exception in a child coroutine launched with 'launch' propagates up, cancelling the parent scope and all its other children, ensuring a "fail-fast" system. Option A is incorrect because the default behavior is not to isolate failures but to propagate them, unlike with a SupervisorJob.

    Read the full bite: Explain Structured Concurrency in Kotlin Coroutines

  16. Question 16 of 30

    Why is `StateFlow` preferred over a cold `Flow` in a ViewModel for exposing UI state that must survive screen rotation?

    Show the answer

    Answer: c · A cold `Flow` would re-execute its data production logic for the new UI after rotation, while `StateFlow` holds the existing state.

    `StateFlow` is a hot, state-holding stream. It maintains its value across UI recreations, providing the latest state to the new UI. A cold `Flow` would restart its producer logic for the new UI collector, causing an unnecessary data reload. The most tempting distractor is the lifecycle-awareness claim, which is true for `LiveData`, not `StateFlow`.

    Read the full bite: Hot vs. Cold Streams: `StateFlow` vs. `Flow`

  17. Question 17 of 30

    Why should a ViewModel use StateFlow instead of a cold Flow for screen state?

    Show the answer

    Answer: d · It broadcasts the latest state to all current collectors without re-running the upstream source for each one

    StateFlow is hot and always holds a current value, so multiple collectors share the same state and receive the latest value immediately without re-triggering the data load. Option B describes cold Flow behavior, which would execute duplicate upstream work for every collector.

    Read the full bite: Hot vs cold Kotlin Flows and StateFlow use case

  18. Question 18 of 30

    Why is StateFlow generally preferred over a regular Flow for exposing UI state from an Android ViewModel?

    Show the answer

    Answer: b · It always holds a current value, replays it to new collectors immediately, and prevents re-execution of upstream logic on re-collection.

    StateFlow is ideal for UI state because it always maintains a current value, immediately provides this value to new observers, and critically, avoids re-triggering the entire data stream (e.g., network calls) when the UI re-collects, such as after a screen rotation. Option A is a common misconception, as StateFlow is not inherently lifecycle-aware and requires explicit scope management for collection.

    Read the full bite: Hot vs. Cold Streams: StateFlow vs. Flow in Android

  19. Question 19 of 30

    An Android ViewModel reads a large file from disk and parses the bytes into objects. Which dispatcher strategy is correct?

    Show the answer

    Answer: b · Perform the read on Dispatchers.IO and the parsing on Dispatchers.Default

    Blocking file reads should use Dispatchers.IO, while CPU-intensive parsing belongs on Dispatchers.Default. Using Default for the read starves its limited thread pool, and using IO for parsing misuses the on-demand thread expansion designed for blocking operations.

    Read the full bite: What is a CoroutineDispatcher and when to use Default versus IO?

  20. Question 20 of 30

    When applying a complex image filter and then uploading the image, which CoroutineDispatchers are best for each task?

    Show the answer

    Answer: a · Filter on Dispatchers.Default, upload on Dispatchers.IO

    Applying a complex filter is CPU-bound and best suited for Dispatchers.Default, which uses a core-limited thread pool. Uploading is I/O-bound and should use Dispatchers.IO, designed for blocking operations with a larger, on-demand thread pool. Using Dispatchers.IO for CPU-bound tasks is inefficient and doesn't leverage the CPU-optimized Default pool.

    Read the full bite: What is a CoroutineDispatcher and when to use IO vs Default?

  21. Question 21 of 30

    Your coroutine performs a blocking network call. Which dispatcher is most suitable to ensure CPU-bound tasks are not starved of their dedicated threads?

    Show the answer

    Answer: d · Dispatchers.IO

    Dispatchers.IO is backed by a large thread pool designed for blocking I/O. Using Dispatchers.Default would block a thread from a smaller pool sized for CPU-intensive work, starving computation.

    Read the full bite: What is a CoroutineDispatcher and when do you use each type?

  22. Question 22 of 30

    What is the primary implication of declaring a generic type parameter with the `out` modifier, as in `interface Source<out T>`?

    Show the answer

    Answer: b · It allows an instance of `Source<String>` to be used where `Source<Any>` is expected.

    The `out` modifier indicates covariance (a producer), which preserves the subtyping relationship. This allows a `Source<subtype>` (like `String`) to be used as a `Source<supertype>` (like `Any`). A common misconception is that `out` implies immutability, but it only enforces type safety.

    Read the full bite: Explain Kotlin's declaration-site variance with `in` and `out`

  23. Question 23 of 30

    What is the primary type-system benefit of declaring an interface as Producer<out T> when T only appears in return positions?

    Show the answer

    Answer: b · It lets Producer<String> be used where Producer<Any> is expected without caller-side wildcards.

    Marking T with out makes Producer covariant, so Producer<String> is a subtype of Producer<Any> and callers never need wildcards. Option C is wrong because variance does not imply immutability; the class may still mutate state via operations that do not mention T.

    Read the full bite: Explain Kotlin's declaration-site variance with in and out

  24. Question 24 of 30

    To maximize type flexibility for a generic interface DataSink<T> that exclusively consumes elements of type T, which variance modifier should be used?

    Show the answer

    Answer: b · in T

    The 'in' modifier is used for contravariant types that act as consumers, allowing a supertype (e.g., DataSink<Any>) to be used where a subtype (e.g., DataSink<String>) is expected, thus maximizing flexibility. The 'out' modifier is for producers, which would be incorrect for a consumer interface.

    Read the full bite: Explain Kotlin's declaration-site variance with in and out

  25. Question 25 of 30

    After reliably reproducing an Android app crash, what is the most efficient next step to identify the root cause?

    Show the answer

    Answer: b · Examine the Logcat output for a "FATAL EXCEPTION" and its associated stack trace.

    The card emphasizes that after reproducing a crash, the immediate and most efficient next step is to check Logcat for the "FATAL EXCEPTION" and its stack trace, which provides the critical initial clue to the crash location. Adding Log.d() statements is an inefficient "shotgun debugging" approach, and setting a breakpoint is typically done after analyzing the initial Logcat output if more detail is needed.

    Read the full bite: How do you debug an app crash in Android Studio?

  26. Question 26 of 30

    After isolating a fatal exception in Logcat, where should you typically set your first breakpoint to debug the root cause?

    Show the answer

    Answer: a · At the deepest frame in your source code before system or library calls

    The deepest frame in your source code is the closest point to the failure under your control, letting you inspect the exact variable state; stopping at the top line often lands inside framework code and misses the actual buggy call site.

    Read the full bite: App crash: debug with Logcat and breakpoints in Android Studio

  27. Question 27 of 30

    After identifying the exact line causing a crash from the Logcat stack trace, what is the most effective next step to understand the root cause?

    Show the answer

    Answer: b · Set a breakpoint a few lines before the crash and run in Debug mode to inspect variable states.

    Setting a breakpoint allows you to interactively inspect the program's state right before the error, revealing the unexpected values causing the crash. While adding Log statements can work, it is a slower, iterative process compared to the interactive debugger.

    Read the full bite: How would you debug an app crash in Android Studio?

  28. Question 28 of 30

    You are building 'free' and 'pro' app versions using product flavors. What is the most secure and efficient way to control access to pro-only features?

    Show the answer

    Answer: a · Use a `buildConfigField` to create a compile-time constant and check it in the code.

    Using `buildConfigField` creates a compile-time constant, allowing the compiler to completely remove pro features from the free build, which is more secure and reduces APK size. Runtime checks or server calls leave the pro code inside the free app.

    Read the full bite: What is a Gradle product flavor?

  29. Question 29 of 30

    How do Gradle product flavors primarily enable the creation of distinct app versions (e.g., free vs. pro) from a single codebase?

    Show the answer

    Answer: c · By generating unique build variants through their combination with build types, which then utilize dedicated source sets for variant-specific code and resources.

    Product flavors combine with build types to form build variants, and these variants then leverage dedicated source sets to include variant-specific code and resources, which is the architectural pattern described. While conditional logic can be used, the card explicitly states this is a 'weak answer' compared to using source sets.

    Read the full bite: Explain Gradle product flavors with a free/pro app example

  30. Question 30 of 30

    To keep pro-only billing code out of the free APK while sharing a common codebase, which approach follows Gradle product-flavor best practices?

    Show the answer

    Answer: b · Add the billing class to src/pro/java and a no-op stub to src/free/java, then reference it from main

    Source-set merging lets Gradle compile only the relevant edition-specific code, keeping the free APK smaller and cleaner. Runtime if-checks on BuildConfig.FLAVOR are a common anti-pattern because they bloat the binary and scatter product logic across the main codebase.

    Read the full bite: Explain what a Gradle product flavor is and give a free vs pro example

Could you explain these out loud?

That is what an interview actually tests. Tezvyn gives you questions like these with what the interviewer is really checking, the answer that lands, and the mistake that ends the conversation, in the four minutes before your next meeting.

The iPhone app is on the way

We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.

Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.

Get it on Google PlayiPhone app coming soon