Answers: Module 01 — Introduction to Kotlin¶
Answer Key¶
Easy Questions (1 pt each)¶
Q1: The ? suffix means the type is nullable — the variable can hold either a value of that type or null. Without ?, the type is non-nullable and the compiler guarantees the variable will never be null.
Q2: val declares an immutable reference — it cannot be reassigned after initialization (like Java's final). var declares a mutable reference — it can be reassigned. Convention: prefer val; use var only when reassignment is necessary. Note: val makes the reference immutable, not the object — a val list: MutableList<Int> still allows list.add(1).
Q3: Google I/O 2017. At the keynote on May 17, 2017, the Android team announced Kotlin as an officially supported and first-class language for Android development.
Q4: The Elvis operator ?:. Example: val name = input ?: "default" — evaluates to "default" when input is null.
Q5: False. Kotlin does not require semicolons. They are optional and by strong convention omitted. Kotlin's grammar does not need semicolons to unambiguously parse statements.
Medium Questions (2 pts each)¶
Q6: A platform type (String!) appears when Kotlin interoperates with Java code that has no null-safety annotations. Because Java's type system does not distinguish null from non-null, the Kotlin compiler cannot determine whether a Java value can be null — so it treats it as a "platform type" — it neither enforces non-null nor forces you to handle null. This is dangerous because the compiler will not warn you about potential NPEs from Java API values. The fix: treat Java return values as nullable (String?) when there is any doubt, or check the Java source for @Nullable/@NotNull annotations.
Q7: listOf(1, 2, 3) creates a read-only List<Int> — no add, remove, or set operations are available. mutableListOf(1, 2, 3) creates a MutableList<Int> with full mutation operations. Choose listOf by default to communicate immutability; choose mutableListOf only when you need to modify the collection after creation.
Q8: Both Kotlin and Java compile to JVM bytecode — the same binary format. The JVM does not know or care which language produced the .class files. Since the bytecode is identical in structure, Kotlin code calling a Java library is calling the same bytecode that javac produced, and the JVM executes it without any bridging layer.
Q9: A single-expression function omits the braces and return keyword, using = instead:
Int in this case). Use single-expression functions when the entire body fits on one readable line.
Q10: Differences between when and Java switch:
1. when is an expression (returns a value); Java switch is a statement (Java 14+ switch expressions exist, but classic switch is a statement)
2. when works with any type — not just integral types and strings like Java switch
3. when supports ranges (in 1..10), type checks (is String), and arbitrary conditions
4. when on a sealed class is exhaustive — the compiler enforces that all cases are handled without requiring else
5. when cases use -> instead of case: and do not fall through by default
Hard Questions (3 pts each)¶
Q11: The bug is using !! on a nullable text. When getLength(null) is called, text!! throws NullPointerException instead of returning 0.
Corrected:
Or as a single expression:Q12:
fun safeDiv(a: Int, b: Int): Int? = if (b != 0) a / b else null
fun printDivision(a: Int, b: Int) {
println(safeDiv(a, b)?.toString() ?: "Division by zero")
}
Q13:
fun area(shape: Shape): Double = when (shape) {
is Circle -> Math.PI * shape.radius * shape.radius
is Rectangle -> shape.width * shape.height
is Triangle -> 0.5 * shape.base * shape.height
}
is Triangle -> ... causes a compile error because when used as an expression on a sealed class must cover all possible subclasses. The compiler tracks the full set of sealed subclasses and reports "when expression must be exhaustive" if any case is missing.
Q14:
fun buildUserSummary(name: String?, age: Int?, city: String?): String {
val parts = mutableListOf<String>()
val displayName = name ?: return "Anonymous user"
parts.add(displayName)
age?.let { parts.add(it.toString()) }
city?.let { parts.add("from $it") }
return parts.joinToString(", ")
}
fun buildUserSummary(name: String?, age: Int?, city: String?): String {
val n = name ?: return "Anonymous user"
return listOfNotNull(n, age?.toString(), city?.let { "from $it" }).joinToString(", ")
}
Expert Questions (5 pts each)¶
Q15: The crash occurs because findEmail is annotated @Nullable in Java. When called from Kotlin, this becomes a platform type String! — the Kotlin compiler does not enforce null-checking. The developer then calls .uppercase() on this potentially-null value without any null check, resulting in an NPE when findEmail returns null in production.
Corrected:
fun notifyUser(userId: Long) {
val email: String? = userService.findEmail(userId)
if (email == null) {
logger.warn("No email found for userId=$userId, skipping notification")
return
}
sendEmail(email.uppercase())
}
// Or more concisely:
fun notifyUser(userId: Long) {
val email = userService.findEmail(userId) ?: run {
logger.warn("No email for userId=$userId")
return
}
sendEmail(email.uppercase())
}
Q16:
sealed class Result<out T>
data class Ok<T>(val value: T) : Result<T>()
data class Err(val exception: Exception) : Result<Nothing>()
fun <T> Result<T>.getOrDefault(default: T): T = when (this) {
is Ok -> value
is Err -> default
}
fun <T, R> Result<T>.map(transform: (T) -> R): Result<R> = when (this) {
is Ok -> try { Ok(transform(value)) } catch (e: Exception) { Err(e) }
is Err -> this // propagate error unchanged
}
fun <T> runCatching(block: () -> T): Result<T> = try {
Ok(block())
} catch (e: Exception) {
Err(e)
}
// Usage example
fun main() {
val result: Result<Int> = runCatching { "42".toInt() }
val doubled: Result<Int> = result.map { it * 2 }
println(doubled.getOrDefault(-1)) // 84
val bad: Result<Int> = runCatching { "abc".toInt() }
println(bad.getOrDefault(-1)) // -1
println((bad as? Err)?.exception?.message) // For input string: "abc"
}
Bonus¶
Bonus 1: Smart casts are automatic type casts performed by the Kotlin compiler after a type-check. After if (x is String), inside that branch the compiler knows x is a String and casts it automatically — no explicit (x as String) needed.
fun printLength(x: Any) {
if (x is String) {
println(x.length) // x is smart-cast to String; .length is available
}
}
Smart cast FAILS when:
1. The variable is a var in scope of another thread — because another thread might reassign it between the check and the use:
var x: Any = "hello"
if (x is String) {
// Smart cast impossible: x is a mutable var; could be reassigned by another thread
// println(x.length) // compile error
println((x as String).length) // explicit cast required
}
class Holder {
val value: Any get() = if (Math.random() > 0.5) "hi" else 42
}
val h = Holder()
if (h.value is String) {
// Smart cast impossible: h.value has a custom getter that might return different types
// println(h.value.length) // compile error
}