Lesson 1 of 83 beginner

Kotlin Basics: val/var, Null Safety, Smart Casts & Data Classes

The fundamentals asked in every Android interview — master these first

Open interactive version (quiz + challenge)

Real-world analogy

val/var is like a permanent marker vs a pencil. Null safety is like a bouncer at a club — you prove your identity (non-null) before you get in, or you get redirected to the 'null entrance' gracefully.

What is it?

Kotlin's type system and core syntax form the foundation of every Android interview. val/var control mutability, null safety eliminates NPE at compile time using the ?, ?., ?:, and !! operators, smart casts reduce boilerplate, when expressions replace switch with power, and data classes generate boilerplate-free value objects.

Real-world relevance

In a field operations enterprise app, you might model a WorkOrder as a data class with nullable assignedTo: Employee? — because orders can be unassigned. Using ?.let ensures you only send push notifications when there IS an assignee, while ?: provides default status text in the UI. Smart casts in a when expression dispatch different sync strategies based on the WorkOrder subtype.

Key points

Code example

// val vs var — prefer val
val BASE_URL = "https://api.fieldops.com"
var retryCount = 0

// Null safety in a real model
data class WorkOrder(
    val id: String,
    val title: String,
    val assignedTo: Employee?,   // nullable — can be unassigned
    val completedAt: Long? = null
)

data class Employee(val id: String, val name: String, val email: String)

// Safe call chain + Elvis
fun getAssigneeName(order: WorkOrder): String {
    return order.assignedTo?.name ?: "Unassigned"
}

// let for null-safe block execution
fun notifyAssignee(order: WorkOrder) {
    order.assignedTo?.let { employee ->
        sendPushNotification(employee.email, order.title)
    }
}

// Smart cast with when expression
fun describeOrder(order: Any): String = when (order) {
    is WorkOrder -> "Work Order: ${order.title}"
    is String    -> "Raw ID: $order"
    else         -> "Unknown: $order"
}

// copy() for immutable state update
fun markComplete(order: WorkOrder, timestamp: Long): WorkOrder {
    return order.copy(completedAt = timestamp)
}

// Destructuring
fun logOrder(order: WorkOrder) {
    val (id, title) = order
    println("Processing order $id: $title")
}

Line-by-line walkthrough

  1. 1. val BASE_URL = ... — compiler enforces this reference can never be reassigned; use for constants and injected dependencies
  2. 2. data class WorkOrder(...) — compiler generates equals/hashCode based on all constructor properties; two WorkOrders with same id/title are equal
  3. 3. val assignedTo: Employee? — the ? makes this property nullable; compiler will reject direct access without a null check
  4. 4. order.assignedTo?.name — safe call: if assignedTo is null, this returns null instead of throwing; chains safely
  5. 5. ?: 'Unassigned' — Elvis: when the left side is null, return this default; right side can also be throw or return
  6. 6. order.assignedTo?.let { employee -> ... } — let block only executes when assignedTo is non-null; employee inside is guaranteed non-null String
  7. 7. is WorkOrder -> order.title — smart cast: compiler knows order is WorkOrder inside this branch, no cast needed
  8. 8. order.copy(completedAt = timestamp) — creates a NEW WorkOrder with all fields copied except completedAt; original is unchanged
  9. 9. val (id, title) = order — destructuring calls order.component1() and order.component2() automatically
  10. 10. fun notifyAssignee is unit-returning — no return type needed; Kotlin infers Unit (equivalent to Java void)

Spot the bug

data class User(var id: Int, var name: String)

fun processUser(input: Any) {
    if (input is User) {
        println(input.name)
    }
    input.name // smart cast here
}

fun getDisplayName(user: User?): String {
    return user!!.name
}
Need a hint?
Two bugs: one with smart cast scope, one with force-unwrapping a nullable parameter
Show answer
Bug 1: 'input.name' outside the if block fails — smart cast only applies INSIDE the is-check block. Fix: move usage inside the if, or cast explicitly. Bug 2: user!!.name will throw KotlinNullPointerException if user is null — the function accepts User? so null is valid input. Fix: return user?.name ?: 'Guest'

Explain like I'm 5

Imagine you have a toy box. val means you can only put toys IN this box (you can't swap the box for a different box). var means you can swap the whole box. Null safety means some boxes have a warning label — 'this might be empty!' — and Kotlin makes you CHECK before reaching in, so you never get hurt by an empty box surprise.

Fun fact

Kotlin's null safety has been shown to eliminate ~35% of Android crash reports that were caused by NullPointerException in Java codebases. Google's internal data showed NPE was the #1 cause of Android app crashes before Kotlin adoption.

Hands-on challenge

Model a school management app's Student data class with: id (non-nullable), name (non-nullable), guardianEmail (nullable), gpa (nullable Double). Write a function that returns a formatted summary string using safe calls, Elvis, and a when expression that returns 'Honors' / 'Passing' / 'At Risk' based on GPA. Write a markPromoted() function using copy().

More resources

Open interactive version (quiz + challenge) ← Back to course: Android Interview Mastery