The questions that come up most often, each with a sample answer you can adapt into your own words. Read them out loud until the explanation feels natural.
How does Kotlin handle null safety?
FundamentalsNullability is part of the type system: a non-null type like String cannot hold null, and you must use the nullable type String? to allow it, which the compiler then forces you to handle. The safe-call operator ?. returns null instead of throwing if the receiver is null, the Elvis operator ?: supplies a default, and !! asserts non-null and throws if wrong. This eliminates most NullPointerExceptions at compile time.
What is the difference between val and var?
Fundamentalsval declares a read-only (immutable) reference that cannot be reassigned after initialisation, while var declares a mutable one. val is preferred by default for safety and clarity. Note that val makes the reference immutable, not the object it points to; a val list reference can still have its contents mutated unless the collection itself is immutable.
What is a coroutine and how does it differ from a thread?
IntermediateA coroutine is a lightweight unit of asynchronous work that can suspend and resume without blocking the underlying thread, so thousands can run on a small thread pool. A thread is a heavier OS-level resource. A suspending function pauses at suspension points and frees its thread for other work, which is why coroutines keep the Android main thread responsive and scale better than thread-per-task on the backend.
What is the difference between launch and async?
Advancedlaunch starts a coroutine that does not return a result (fire-and-forget), returning a Job you can cancel or join. async starts a coroutine that computes a value and returns a Deferred, whose result you obtain with await. Use launch for side-effecting work and async when you need a result, especially to run several computations concurrently and await them together.
What is a data class?
IntermediateA data class is a class declared with the data keyword for which the compiler auto-generates equals(), hashCode(), toString(), and copy() based on the properties in the primary constructor, plus componentN() functions for destructuring. It is ideal for value/DTO types and removes the boilerplate Java requires. copy() makes it easy to produce a modified duplicate while keeping immutability.
What is a sealed class and why use one?
AdvancedA sealed class restricts its subclasses to a known set declared in the same module, so the compiler knows every possible subtype. This makes a when expression over it exhaustive without an else branch, and the compiler flags you if you add a subtype and forget to handle it. It is the idiomatic way to model a closed set of states, like a UI state of Loading, Success, or Error.
What is an extension function?
IntermediateAn extension function lets you add a method to an existing type without modifying or inheriting from it, by declaring fun Type.newMethod(). It is resolved statically (it does not actually modify the class) and is widely used to add readable helpers to standard or third-party types. The Kotlin standard library is built largely from extension functions on collections and strings.
What is a platform type and why is it a risk?
AdvancedWhen Kotlin calls Java code, the Java type's nullability is usually unknown, so Kotlin treats it as a platform type (written String!), trusting you to know whether it can be null. The risk is that you can use it as non-null without a compiler check, so a Java method returning null can still cause an NPE at runtime. The mitigation is to annotate Java with @Nullable/@NonNull or defensively treat interop values as nullable.
What are the scope functions (let, run, apply, also, with)?
IntermediateScope functions run a block against an object and differ on two axes: how the object is referenced inside (it for let and also, this for run, apply, and with) and what the call returns (the lambda result for let, run, and with; the object itself for apply and also). The common uses: let for null-safe transformation chained after ?., apply for configuring an object you are building, and also for side effects like logging without breaking a chain. Overusing them harms readability, and saying so is part of the good answer.
val user = User().apply {
name = "Ada"
email = "ada@example.com"
}
val length = nickname?.let { it.trim().length } ?: 0
What is the difference between lateinit and lazy?
AdvancedBoth defer initialisation of a property. lateinit applies to a mutable var of a non-null reference type, promises the compiler you will assign it before first use (dependency injection, Android lifecycle setup), and throws if you break that promise. by lazy applies to a val: the initialiser block runs once on first access, the result is cached, and access is thread-safe by default. Rule of thumb: lazy when you can compute the value in one expression and want it on demand, lateinit when a framework hands you the value after construction.
class ProfileScreen {
lateinit var binding: ProfileBinding
val formatter: DateTimeFormatter by lazy {
DateTimeFormatter.ofPattern("dd MMM yyyy")
}
}
What is the difference between == and === in Kotlin?
Fundamentals== in Kotlin is structural equality: it calls equals() under the hood, with a null-safe check first, so two data class instances with the same property values compare equal. === is referential equality: true only when both sides point to the same object instance. This is the reverse of Java's habits, where == on objects compares references, and interviewers use the question to check you have adjusted. In practice you use == almost always and === only when identity itself is the question, such as in caching or sentinel checks.
What is a companion object?
IntermediateA companion object is a singleton declared inside a class that plays the role of Java's static members, since Kotlin has no static keyword. It holds factory functions, constants, and shared state tied to the class rather than to instances, and its members are called through the class name. Unlike statics, a companion is a real object: it can implement interfaces and be passed around. The idiomatic factory pattern is a companion object exposing a creation function, and @JvmStatic exposes members as genuine statics for Java interop.