Bird
Raised Fist0
Kotlinprogramming~15 mins

Smart casts in when and if in Kotlin - Deep Dive

Choose your learning style10 modes available
Overview - Smart casts in when and if
What is it?
Smart casts in Kotlin automatically convert a variable to a more specific type when the compiler can guarantee its type after a check. This means you don't have to manually cast the variable after checking its type with conditions like if or when. It makes code safer and cleaner by reducing explicit casts and potential errors.
Why it matters
Without smart casts, programmers must manually convert types after checking them, which is error-prone and verbose. Smart casts simplify code and prevent bugs by ensuring type safety automatically. This leads to clearer, more readable code and fewer runtime errors, improving developer productivity and software reliability.
Where it fits
Before learning smart casts, you should understand Kotlin's basic types, type checking with 'is', and control flow statements like if and when. After mastering smart casts, you can explore advanced Kotlin features like sealed classes, type inference, and null safety in more depth.
Mental Model
Core Idea
Smart casts let Kotlin automatically treat a variable as a specific type after a successful type check, without needing manual casting.
Think of it like...
It's like checking the label on a box before opening it, so you know exactly what's inside and can handle it properly without guessing or extra steps.
┌───────────────┐
│ Variable x    │
│ (Unknown type)│
└──────┬────────┘
       │ type check (is String?)
       ▼
┌───────────────┐
│ Smart cast x  │
│ (Now String)  │
└───────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding type checks with is
🤔
Concept: Learn how Kotlin checks variable types using the 'is' keyword.
In Kotlin, you can check if a variable is of a certain type using 'is'. For example: val obj: Any = "Hello" if (obj is String) { println("It's a string") } else { println("Not a string") } This checks if obj is a String and prints accordingly.
Result
Output: It's a string
Understanding type checks is the first step to using smart casts, as they rely on these checks to safely treat variables as specific types.
2
FoundationManual casting without smart casts
🤔
Concept: See how casting works manually when smart casts are not used.
Before smart casts, after checking a type, you had to cast manually: val obj: Any = "Hello" if (obj is String) { val str = obj as String println(str.length) } Here, even after checking, you must cast obj to String explicitly.
Result
Output: 5
Manual casting is repetitive and can cause runtime errors if done incorrectly, motivating the need for smart casts.
3
IntermediateUsing smart casts in if statements
🤔Before reading on: do you think Kotlin requires manual casting after an 'is' check in an if statement? Commit to your answer.
Concept: Kotlin automatically casts variables after a successful 'is' check inside if blocks.
With smart casts, Kotlin lets you use the variable as the checked type without manual casting: val obj: Any = "Hello" if (obj is String) { println(obj.length) // obj is smart cast to String } No explicit cast is needed.
Result
Output: 5
Knowing that Kotlin smart casts variables after type checks in if statements reduces boilerplate and improves safety.
4
IntermediateSmart casts in when expressions
🤔Before reading on: do you think smart casts work inside when branches after type checks? Commit to your answer.
Concept: Kotlin smart casts variables inside when branches after type checks, allowing direct use of specific types.
Example: fun describe(obj: Any) = when (obj) { is String -> "String of length ${obj.length}" is Int -> "Integer value $obj" else -> "Unknown type" } Here, obj is smart cast to String or Int in each branch.
Result
describe("Hi") returns "String of length 2"
Smart casts in when expressions enable concise and type-safe multi-branch logic without manual casts.
5
IntermediateLimitations: mutable variables and smart casts
🤔Before reading on: do you think smart casts work on mutable variables (var) after type checks? Commit to your answer.
Concept: Smart casts only work on immutable variables (val) or local variables that can't change between check and use.
Example: var obj: Any = "Hello" if (obj is String) { // obj.length is NOT smart cast here because obj is mutable // You must cast manually println((obj as String).length) } Because obj can change, Kotlin won't smart cast it.
Result
Output: 5 (with manual cast)
Understanding this limitation prevents confusion and runtime errors when working with mutable variables.
6
AdvancedSmart casts with nullable types
🤔Before reading on: do you think smart casts automatically handle nullable types after null checks? Commit to your answer.
Concept: Kotlin smart casts nullable variables to non-null types after explicit null checks.
Example: val str: String? = "Hello" if (str != null) { println(str.length) // smart cast to non-null String } Kotlin knows str can't be null inside the if block.
Result
Output: 5
Knowing smart casts handle nullability improves safe handling of nullable types without extra casts.
7
ExpertSmart casts and custom getters or multi-threading
🤔Before reading on: do you think smart casts always work with properties that have custom getters or in multi-threaded code? Commit to your answer.
Concept: Smart casts do not work reliably on properties with custom getters or mutable shared state because the value can change unexpectedly.
Example: class Example { val prop: String get() = "Hello" } val ex = Example() if (ex.prop is String) { // ex.prop is NOT smart cast because getter can return different values // Must cast manually if needed } Similarly, in multi-threaded code, smart casts are unsafe on mutable shared variables.
Result
Compiler error if you try to smart cast here
Understanding these edge cases prevents subtle bugs and compiler errors in complex code.
Under the Hood
Kotlin's compiler tracks type checks and control flow to determine when a variable's type is guaranteed. When it detects a successful 'is' check and no changes to the variable, it automatically treats the variable as the checked type in that scope. This is done at compile time, so no runtime overhead occurs. The compiler also verifies mutability and possible changes to ensure safety.
Why designed this way?
Smart casts were designed to reduce boilerplate and errors from manual casting, improving code readability and safety. The compiler-based approach ensures zero runtime cost and strong guarantees. Alternatives like runtime casts or unchecked casts were less safe or verbose, so smart casts balance safety and convenience.
┌───────────────┐
│ Source code   │
│ with 'is'     │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Kotlin        │
│ Compiler      │
│ analyzes flow │
│ and mutability│
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Smart cast    │
│ applied if    │
│ safe          │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Generated     │
│ bytecode uses │
│ variable as   │
│ specific type │
└───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Do smart casts work on mutable variables (var) after type checks? Commit to yes or no.
Common Belief:Smart casts always work after type checks regardless of variable mutability.
Tap to reveal reality
Reality:Smart casts only work on immutable variables (val) or local variables that cannot change between check and use.
Why it matters:Assuming smart casts work on mutable variables leads to compiler errors or unsafe casts causing runtime crashes.
Quick: Do smart casts apply to properties with custom getters? Commit to yes or no.
Common Belief:Smart casts apply to all variables and properties after type checks.
Tap to reveal reality
Reality:Smart casts do not apply to properties with custom getters because their value can change unexpectedly.
Why it matters:Misusing smart casts on such properties can cause incorrect assumptions about type safety and lead to bugs.
Quick: After a null check, is a nullable variable always smart cast to non-null? Commit to yes or no.
Common Belief:Kotlin always smart casts nullable variables to non-null after null checks.
Tap to reveal reality
Reality:Smart casts only work if the variable is immutable or local and cannot change; otherwise, the cast is not applied.
Why it matters:Incorrect assumptions about null smart casts can cause null pointer exceptions if the variable changes unexpectedly.
Quick: Does smart casting add runtime overhead? Commit to yes or no.
Common Belief:Smart casts add extra runtime checks and slow down the program.
Tap to reveal reality
Reality:Smart casts are a compile-time feature with no runtime overhead; the compiler generates code as if the variable was manually cast.
Why it matters:Believing smart casts slow down code may discourage their use, missing out on safer and cleaner code.
Expert Zone
1
Smart casts depend on control flow analysis, so complex code with multiple branches or loops can affect their applicability.
2
Smart casts do not work across function boundaries; the compiler only tracks variable types within the current scope.
3
Using smart casts with sealed classes and when expressions enables exhaustive checks, improving code safety.
When NOT to use
Avoid relying on smart casts for mutable shared state, properties with custom getters, or variables that can change between check and use. Instead, use explicit casts or safe calls. For multi-threaded code, consider synchronization or immutable data structures.
Production Patterns
In production Kotlin code, smart casts are widely used in null safety checks, sealed class handling with when, and type-specific logic in if branches. They reduce boilerplate and improve readability, especially in Android apps and backend services.
Connections
Type inference
Builds-on
Understanding smart casts helps grasp how Kotlin infers types in expressions, making code concise and safe.
Pattern matching in functional languages
Similar pattern
Smart casts in Kotlin resemble pattern matching in languages like Scala or Haskell, where types are checked and variables are automatically treated as specific types.
Quality control in manufacturing
Analogous process
Just as quality control checks items before processing them differently, smart casts check variable types before treating them specifically, ensuring safety and correctness.
Common Pitfalls
#1Assuming smart casts work on mutable variables and skipping manual casts.
Wrong approach:var obj: Any = "Hello" if (obj is String) { println(obj.length) // Compiler error: smart cast not possible }
Correct approach:var obj: Any = "Hello" if (obj is String) { println((obj as String).length) // Manual cast needed }
Root cause:Misunderstanding that smart casts require immutability or guaranteed stability of the variable.
#2Expecting smart casts on properties with custom getters.
Wrong approach:class Example { val prop: String get() = "Hello" } val ex = Example() if (ex.prop is String) { println(ex.prop.length) // Compiler error }
Correct approach:class Example { val prop: String get() = "Hello" } val ex = Example() if (ex.prop is String) { println((ex.prop as String).length) // Manual cast }
Root cause:Not realizing that custom getters can return different values, so smart casts are unsafe.
#3Ignoring null checks on mutable nullable variables expecting smart casts.
Wrong approach:var str: String? = "Hello" if (str != null) { println(str.length) // Compiler error }
Correct approach:var str: String? = "Hello" if (str != null) { println((str as String).length) // Manual cast }
Root cause:Assuming smart casts apply regardless of mutability and possible changes.
Key Takeaways
Smart casts let Kotlin automatically treat variables as specific types after successful type checks, reducing manual casting.
They only work on immutable or stable variables where the compiler can guarantee the type won't change.
Smart casts improve code safety and readability by preventing unnecessary casts and potential runtime errors.
Limitations exist for mutable variables, properties with custom getters, and multi-threaded scenarios where smart casts do not apply.
Understanding smart casts unlocks more idiomatic and concise Kotlin code, especially in if and when expressions.

Practice

(1/5)
1. What does Kotlin's smart cast feature do inside an if or when block?
easy
A. Disables type checking inside the block
B. Requires explicit casting of variables after type checks
C. Automatically treats a variable as a specific type after a type check
D. Converts all variables to strings inside the block

Solution

  1. Step 1: Understand smart cast purpose

    Smart casts let Kotlin automatically treat a variable as a more specific type after you check its type.
  2. Step 2: Apply to if/when blocks

    Inside if or when, Kotlin knows the variable's type and lets you use it without manual casting.
  3. Final Answer:

    Automatically treats a variable as a specific type after a type check -> Option C
  4. Quick Check:

    Smart cast = automatic type use after check [OK]
Hint: Smart casts auto-change type after check in if/when [OK]
Common Mistakes:
  • Thinking manual cast is always needed
  • Assuming smart casts disable type checks
  • Believing variables convert to string automatically
2. Which of the following is the correct syntax to use smart casts in a when expression for variable obj?
easy
A. when obj { is String -> println(obj.length) else -> println("Not a string") }
B. when (obj) { obj is String -> println(obj.length) else -> println("Not a string") }
C. when (obj) { String -> println(obj.length) else -> println("Not a string") }
D. when (obj) { is String -> println(obj.length) else -> println("Not a string") }

Solution

  1. Step 1: Check correct when syntax

    The when expression requires parentheses around the variable: when (obj).
  2. Step 2: Use type check correctly

    The correct type check is is String without repeating variable name inside the block.
  3. Final Answer:

    when (obj) { is String -> println(obj.length) else -> println("Not a string") } -> Option D
  4. Quick Check:

    Correct when syntax uses parentheses and is [OK]
Hint: Use parentheses and 'is' keyword in when [OK]
Common Mistakes:
  • Omitting parentheses around variable
  • Using variable name inside when branches
  • Using type name without 'is' keyword
3. What will be the output of this Kotlin code?
fun test(obj: Any) {
    when (obj) {
        is String -> println(obj.length)
        is Int -> println(obj + 1)
        else -> println("Unknown")
    }
}

test("Hello")
test(5)
test(3.14)
medium
A. 5 6 7
B. 5 6 Unknown
C. Hello 6 Unknown
D. 5 5 Unknown

Solution

  1. Step 1: Analyze each call

    For test("Hello"), obj is String, so prints length 5.
    For test(5), obj is Int, so prints 5 + 1 = 6.
    For test(3.14), no matching type, prints "Unknown".
  2. Step 2: Confirm output lines

    Outputs are:
    5
    6
    Unknown
  3. Final Answer:

    5 6 Unknown -> Option B
  4. Quick Check:

    Smart cast uses type to choose output [OK]
Hint: Match type in when to get output [OK]
Common Mistakes:
  • Confusing string length with string itself
  • Adding 1 to string instead of int
  • Expecting default else to print number
4. Identify the error in this Kotlin code using smart casts:
fun check(obj: Any) {
    if (obj is String) {
        println(obj.length)
    } else if (obj is Int) {
        println(obj + 1)
    } else {
        println(obj.length)
    }
}
medium
A. Trying to access length on obj in else block without type check
B. Missing parentheses in if condition
C. Using else if instead of when
D. Smart casts do not work inside if blocks

Solution

  1. Step 1: Review else block usage

    In the else block, obj can be any type, but code tries to access obj.length which is invalid for non-String types.
  2. Step 2: Understand smart cast limits

    Smart casts only apply after type checks. Here, no type check before accessing length in else block causes error.
  3. Final Answer:

    Trying to access length on obj in else block without type check -> Option A
  4. Quick Check:

    Access property only after type check [OK]
Hint: Check type before accessing properties [OK]
Common Mistakes:
  • Assuming else block has smart cast automatically
  • Confusing else if with when
  • Thinking smart casts fail inside if blocks
5. Given a list of Any objects, write a Kotlin function using when with smart casts to return a list of strings describing each element's type and value. What is the correct implementation?
fun describeList(items: List<Any>): List<String> {
    return items.map { item ->
        when (item) {
            is String -> "String of length ${'$'}{item.length}"
            is Int -> "Int with value ${'$'}item"
            else -> "Unknown type"
        }
    }
}
hard
A. Correct as is, smart casts allow using item.length and item directly
B. Needs explicit casts like (item as String).length and (item as Int)
C. Cannot use smart casts inside lambda expressions
D. Must use if-else instead of when for smart casts

Solution

  1. Step 1: Check smart cast usage in when

    The when expression smart casts item to String or Int automatically after type check.
  2. Step 2: Confirm lambda and map usage

    Inside the lambda, smart casts work normally, so accessing item.length and item is valid without explicit casts.
  3. Final Answer:

    Correct as is, smart casts allow using item.length and item directly -> Option A
  4. Quick Check:

    Smart casts work inside lambdas and when [OK]
Hint: Smart casts work inside lambdas and when blocks [OK]
Common Mistakes:
  • Adding unnecessary explicit casts
  • Thinking smart casts don't work in lambdas
  • Replacing when with if-else unnecessarily
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard