Question 1
Difficulty: medium
How do you decide when to use a data class, sealed class, or enum class in Kotlin?
Sample answer
I choose based on the kind of state I need to model. I use a data class when I need a lightweight object whose main purpose is to hold values, especially if I want generated functions like copy, equals, and toString. A sealed class is my first choice when I need a closed set of related types with different behavior, like representing UI states, network results, or events. It gives me strong type safety and makes when expressions exhaustive. I use an enum class when the values are fixed and simple, like user roles, priorities, or filter modes, especially if each value is just a label or a small amount of shared data. In practice, I try not to force one option everywhere. For example, I’d use a sealed class for loading/success/error states rather than an enum, because each state often carries different data. That leads to cleaner code and fewer null checks.
Question 2
Difficulty: medium
How do you handle null safety in Kotlin while keeping your code readable and maintainable?
Sample answer
I treat null safety as a design tool, not just a syntax feature. My first goal is to prevent nulls from flowing through the system unnecessarily, so I prefer non-null types by default and only allow nullable values where they make sense, such as optional API fields or data from legacy code. When a value can be null, I use safe calls, Elvis operators, and let blocks where they improve clarity. I avoid chaining too many operators in one line if it makes the intent hard to follow. In those cases, I break the logic into smaller steps or use helper functions. I also pay attention to where nulls originate. For example, if a repository returns nullable data often, I’d rather improve that contract than keep patching callers. In reviews, I look for unsafe calls and unnecessary force unwraps, because they usually hide assumptions that will break later. Good null safety should make code safer and easier to reason about, not more verbose.
Question 3
Difficulty: hard
Tell me about a time you optimized performance in a Kotlin or Android project.
Sample answer
In one project, we had a screen that felt sluggish when loading a large list with images and calculated fields. I started by measuring instead of guessing, using profiling tools and logging to see where the delay was happening. The main issue was that we were doing too much work on the main thread while mapping network models into UI models. I moved the heavier transformation work off the UI thread using coroutines and structured it so only the final rendering happened on the main thread. I also reduced unnecessary object creation in the adapter and replaced some repeated calculations with precomputed values in the view model. Another improvement came from switching to a more efficient list update strategy so only changed items were refreshed. The result was smoother scrolling and noticeably faster screen load time. What I learned is that performance work is usually a mix of measurement, prioritization, and small targeted fixes rather than one big change.
Question 4
Difficulty: hard
How do you structure coroutine usage in a Kotlin application to avoid leaks and make cancellation safe?
Sample answer
I like to keep coroutine scope tied to the lifecycle of whatever owns the work. In Android, that usually means using viewModelScope for UI-related operations and lifecycle-aware scopes for view-level work. For longer-running or shared operations, I make sure the scope is owned by a clear component rather than creating ad hoc global scopes. I also try to keep suspend functions focused and cancellation-friendly by avoiding blocking calls inside them. If I need to call a blocking API, I move it to Dispatchers.IO or wrap it in the proper dispatcher. Another habit I follow is propagating cancellation rather than swallowing exceptions. If a coroutine is canceled, I want it to stop quickly and predictably. For error handling, I separate expected failures from unexpected ones so the UI can react properly. In teams, I also like to standardize patterns for launching work, collecting flows, and handling retry logic. That keeps coroutine usage consistent and easier to debug.
Question 5
Difficulty: medium
Explain how you would use Flow in Kotlin and when you would prefer it over LiveData or a simple suspend function.
Sample answer
I use Flow when I need a stream of values over time rather than a single result. It works well for things like UI state, search input, pagination events, cache updates, or any source that can emit repeatedly. Compared with a suspend function, Flow is better when the consumer may need updates after the initial response. Compared with LiveData, I prefer Flow when I want more control over operators, transformations, and testing, especially in a modern Kotlin codebase. That said, I don’t use Flow everywhere just because it exists. If I only need one asynchronous result, a suspend function is simpler and easier to read. If I need lifecycle-aware UI observation in an existing Android app, LiveData can still be appropriate, especially in a legacy codebase. My general rule is to choose the simplest abstraction that fits the problem. When I do use Flow, I’m careful about cold versus hot streams, backpressure behavior, and where collection happens so I don’t accidentally do expensive work multiple times.
Question 6
Difficulty: medium
Describe a time you disagreed with a teammate about implementation details. How did you handle it?
Sample answer
I’ve had situations where a teammate and I wanted different approaches for the same feature, usually around architecture or how much logic should live in the view layer. In one case, I preferred moving the mapping and business rules into the view model, while my teammate wanted to keep everything close to the UI for speed. Instead of arguing from preference, I suggested we compare both options against maintainability, testability, and delivery risk. We sketched the flow together, identified what would be easy to test, and looked at how the code would age if requirements changed. That conversation made it clear that separating the logic would reduce duplication and make future changes safer. I also made sure not to frame it as my way versus theirs. The goal was to get the best result for the team. We agreed on a small implementation, reviewed the tradeoffs, and moved forward without slowing the sprint. I think the key is to stay respectful, stay technical, and keep the focus on outcomes.
Question 7
Difficulty: medium
How do you design Kotlin code so it is easy to test?
Sample answer
I try to design for testability from the start instead of adding tests around hard-to-test code later. That usually means keeping business logic out of UI classes and pushing it into small, focused functions or use-case classes. I prefer dependency injection so I can swap real services with fakes or mocks in tests. I also keep side effects isolated, which makes it easier to test logic without needing a full environment. For example, if a function transforms API data into UI state, I keep that transformation pure and deterministic. Then the test only needs input and expected output. For coroutine-based code, I make sure dispatchers and time-related behavior can be controlled in tests. I also like to write tests around behavior, not implementation details, so they remain useful when the code evolves. In my experience, code that is easy to test is usually also easier to read and refactor. If something feels too difficult to test, that is often a sign the design needs another look.
Question 8
Difficulty: hard
What steps do you take when debugging a Kotlin bug that only appears in production?
Sample answer
I start by narrowing down the difference between production and local environments. Often the bug is caused by data shape, timing, device-specific behavior, or a condition that is hard to reproduce. I look at logs, crash reports, analytics, and any contextual data we captured around the failure. If the issue is not obvious, I try to reproduce it with the same input, same build variant, and as close an environment as possible. I also review recent changes to see if a new edge case was introduced. In Kotlin code, I pay special attention to null handling, coroutine cancellation, race conditions, and serialization issues because those can be subtle in production. If I still cannot reproduce it, I add targeted logging or temporary telemetry in a safe way so we can learn more without overwhelming the app. Once I have a likely cause, I fix the root issue and add a regression test so the problem does not come back. I prefer to solve the underlying pattern, not just the symptom.
Question 9
Difficulty: medium
How would you approach building a new feature from scratch in an existing Kotlin codebase?
Sample answer
I would start by understanding the feature goals, the current architecture, and any constraints from the existing codebase. Before writing code, I’d clarify the data flow, the user experience, and the edge cases so I don’t build something that needs major rework later. Then I’d identify the cleanest place to add the logic, whether that is a new use case, repository method, view model function, or API integration. If the codebase already has conventions for naming, state handling, or error management, I follow them unless there is a strong reason not to. I also like to break the feature into small deliverables so it can be reviewed and tested early. For example, I might first add the domain logic, then wire in the UI state, then handle retries and error cases. While doing that, I keep an eye on readability and consistency with the rest of the project. My goal is not just to ship the feature, but to integrate it in a way that future developers can understand and extend easily.
Question 10
Difficulty: easy
What Kotlin language features do you use most often, and why do they help you write better code?
Sample answer
The Kotlin features I use most are data classes, extension functions, scope functions, sealed classes, and higher-order functions. Data classes reduce boilerplate and make state objects clearer. Extension functions help me keep utility logic close to the type it works with, which improves readability without cluttering the class itself. Scope functions like let, apply, and also are useful, but I use them carefully because they can become confusing if overused. Sealed classes help me model state and outcomes in a way that is explicit and type-safe. Higher-order functions are great for reducing duplication, especially when the same workflow needs slightly different behavior. I also value Kotlin’s null-safety and smart casting because they let me write code that is both concise and safer. What I like most is that these features encourage expressive code without sacrificing structure. When used well, Kotlin lets me focus more on intent and less on boilerplate. I still try to keep the code straightforward, though, because readability matters more than clever syntax.